<?xml version="1.0" encoding="UTF-8"?><d:tdl xmlns="http://www.w3.org/1999/xhtml" xmlns:b="http://www.backbase.com/2006/btl" xmlns:bb="http://www.backbase.com/2006/client"  xmlns:d="http://www.backbase.com/2006/tdl" >

	<d:namespace name="http://www.backbase.com/2006/btl">

		<d:uses element="containerElement dimensionElement" src="../visualElement/visualElement.xml"/>
		<d:uses behavior="dragBase" src="../dragAndDrop/dragAndDrop.xml"/>
		<d:uses behavior="resizeBase" src="../resize/resize.xml"/>
		<d:uses behavior="focusableElement" src="../focus/focus.xml"/>

		<d:element name="gridBase" extends="b:containerElement b:dimensionElement b:resizeBase b:focusableElement" abstract="true">
			

			

			<d:resource type="text/css" name="gridBase_css"><![CDATA[.btl-grid-grid {
	position: relative;
	border: 1px solid #CCCCCC;
	outline: none;
}
.btl-grid-port {
	overflow: hidden;
	position: relative;
	-moz-outline: none;
}
.btl-grid-data-container {
	height: auto;
	overflow-y: hidden;
	overflow-x: auto;
	position: relative;
	-moz-outline: none;
}
.btl-grid-fridge {
	position: absolute;
	top: 0;
	left: 0;
	overflow: hidden;
	background-color: white;
	border-right: 2px solid #CCCCCC;
	-moz-outline: none;
}
.btl-grid-row-splitter {/* splitter between top and bottom data areas*/
	height: 2px;
	line-height: 2px;
	font-size: 2px;
	background-color: #CCCCCC;/* the same as borders*/
	cursor: n-resize;
}
.btl-grid-row-splitter-content {
	height: 1px;
	line-height: 1px;
	background-color: white;
}
.btl-grid-scrollers {
	overflow: hidden;
	border-left: 1px solid #CCCCCC;
	position: absolute;
	top: 0;
	right: 0;
	-moz-outline: none;
}
.btl-grid-vscroller {
	overflow-x: visible;
	overflow-y: auto;
	position: relative;
	margin-left: -1px;
	-moz-outline: none;
}

.btl-grid-busy-indicator {
	width: 16px;
	height: 16px;
	position: absolute;
	right: 0;
	top: 0;
	background-position: 0 0;
	background-image: url(media/loading.gif);
	background-repeat: no-repeat;

}
.btl-grid-column-insert-indicator {
	left: -1000px;
	top: -1000px;
	position: absolute;
	width: 10px;
	height: 10px;
	margin-left: -5px;
	font-size: 1px;
	line-height: 1px;
	background: transparent url(media/red.png) no-repeat;
}
.btl-grid-table {
	table-layout: fixed;
	border-spacing: 0;
	width: 0;
}
td.btl-grid-data, td.btl-grid-header {
	padding: 0px 3px;
	white-space: nowrap;
	cursor: default;
	overflow: hidden;
	text-overflow: ellipsis;
}
.btl-grid-clear td, .btl-grid-clear th {
	border-style: none !important;
	padding: 0 !important;
	line-height: 1px;
}
/* Resize styles */
.btl-grid-eResizeCursor {
	cursor: e-resize !important;
}
.btl-grid-sResizeCursor {
	cursor: s-resize !important;
}
.btl-grid-resize-shadow {
	left: 0;
	top: 0;
	position: absolute;
	font-size: 1px;
	line-height: 1px;
	background-color: #999999;
}
.btl-grid-resize-dummy {
	left: 0;
	top: 0;
	position: absolute;
	font-size: 1px;
	line-height: 1px;
	height: 1px;
}
/* Drag and drop styles */
.btl-grid-drag-dummy {
	left: -1000px;
	top: -1000px;
	position: absolute;
	z-index: 1000;
	cursor: move;
}
.btl-grid-iframe {
	position: relative;
	border: none;
	height: 100%;
	padding: 0;
}]]></d:resource>
			<d:resource type="text/javascript"><![CDATA[// --------------------------- btlGrid ---------------------------
if (!btl.gridBase) {
	btl.gridBase = {};

	/**
	 * Returns actual scrollHeight (Opera calculates the property incorrectly)
	 */
	btl.gridBase.getScrollHeight = function btl_gridBase_getScrollHeight(oElm) {
		//Opera has wrong scrollHeight value
		if (bb.browser.opera)
			return oElm.scrollHeight - (oElm.scrollWidth > oElm.offsetWidth ? bb.html.getScrollBarWidth() : 0);
		return oElm.scrollHeight;
	}

	/**
	 * Splits a string on spaces into an array
	 * @param {String} value String to split
	 * @return The resulting array
	 * @type Array
	 */
	btl.gridBase.split = function btl_gridBase_split(sValue) {
		var aValue = sValue.split(/\s+/);
		if (aValue.length && !aValue[0].length)
			aValue.shift();
		if (aValue.length && !aValue[aValue.length - 1].length)
			aValue.pop();
		return aValue;
	}

	/**
	 * Finds children with specified tag name
	 * @param {Object} elm     Parent element (view node)
	 * @param {String} type    Tag name to find
	 */
	btl.gridBase.getChildrenByTagName = function btl_gridBase_getChildrenByTagName(oElm, sTag) {
		var aRes = [];
		sTag = sTag.toUpperCase();
		for (var oNode = oElm.firstChild; oNode; oNode = oNode.nextSibling) {
			if (oNode.nodeType == 1 && oNode.tagName == sTag)
				aRes.push(oNode)
		}
		return aRes;
	}

	/**
	 * Calculates actual column indexes in a table having cells with rowspan/colspan attributes
	 * @param {Object} table Table element to work on
	 * @return A two-dimensional array with first rows, then cells, containing instances of the Range
	 * object which indicates w.
	 * @type Array
	 */
	btl.gridBase.computeCellRanges = function btl_gridBase_computeCellRanges(eTable) {
		var aVirtTable = [];
		var iNumColumns = 0;
		var aIndexes = [];
		var aRows = eTable.rows;
		var oRange;
		for (var iRowIndex = 0; iRowIndex < aRows.length; iRowIndex++) {
			var aCells = aRows[iRowIndex].cells;
			aIndexes[iRowIndex] = [];
			for (var iCellIndex = 0, iMax = aCells.length; iCellIndex < iMax; iCellIndex++) {
				var oCell = aCells[iCellIndex];
				var iRowSpan = oCell.rowSpan || 1;
				var iColSpan = oCell.colSpan || 1;
				var iFirstCol;

				if (!(aVirtTable[iRowIndex]))
					aVirtTable[iRowIndex] = [];
				// Find the first available column index in the row
				for (var i = 0; i < aVirtTable[iRowIndex].length + 1; i++) {
					if (!aVirtTable[iRowIndex][i]) {
						iFirstCol = i;
						break;
					}
				}
				oRange = new btl.gridBase.Range(iFirstCol, iFirstCol + iColSpan - 1);
				aIndexes[iRowIndex][iCellIndex] = oRange;
				for (var i = iRowIndex; i < iRowIndex + iRowSpan; i++) {
					if (!aVirtTable[i])
						aVirtTable[i] = [];
					var aVirtRow = aVirtTable[i];
					for (var j = iFirstCol; j < iFirstCol + iColSpan; j++) {
						aVirtRow[j] = true;
					}
				}
			}//iCellIndex
			if (oRange)
				iNumColumns = Math.max(iNumColumns, oRange.last);
		}//iRowIndex

		// XXX: no longer used??
		aIndexes.numColumns = iNumColumns + 1;//number of columns
		return aIndexes;
	}

	/**
	 * Calculates the sizes of borders.
	 * @param {Element} An element to get border sizes of.
	 * @return Object with "top", "bottom", "left", "right", "leftright" and "topbottom"
	 * properties containing sizes of correspondent borders.
	 * @type Object
	 */
	btl.gridBase.getBorderSizes = function btl_gridBase_getBorderSizes(oViewNode) {
		oBorder = {
			'top'    : bb.html.convertToPixels(bb.html.getStyle(oViewNode, 'border-top-width'), oViewNode),
			'bottom' : bb.html.convertToPixels(bb.html.getStyle(oViewNode, 'border-bottom-width'), oViewNode),
			'left'   : bb.html.convertToPixels(bb.html.getStyle(oViewNode, 'border-left-width'), oViewNode),
			'right'  : bb.html.convertToPixels(bb.html.getStyle(oViewNode, 'border-right-width'), oViewNode)
		};
		oBorder.leftright = oBorder.left + oBorder.right;
		oBorder.topbottom = oBorder.top + oBorder.bottom;
		return oBorder;
	}

	/**
	 * Converts a CSS unit to pixels. Percentages are relative to the parent's width.
	 * @param sUnit The value in CSS units.
	 * @param oViewNode The viewNode to compute for.
	 * @return Number of pixels
	 * @type Integer
	 */
	btl.gridBase.unitToPixelWidth = function btl_gridBase_unitToPixelsWidth(sUnit, oViewNode) {
		if (sUnit.indexOf('%') >= 0)
			return Math.floor(parseFloat(sUnit) * bb.html.getBoxObject(oViewNode.parentNode, 'content').width / 100);
		else
			return bb.html.convertToPixels(sUnit, oViewNode);
	}

	/**
	 * Converts a CSS unit to pixels. Percentages are relative to the parent's height.
	 * @param sUnit The value in CSS units.
	 * @param oViewNode The viewNode to compute for.
	 * @return Number of pixels
	 * @type Integer
	 */
	btl.gridBase.unitToPixelHeight = function btl_gridBase_unitToPixelsHeight(sUnit, oViewNode) {
		if (sUnit.indexOf('%') >= 0)
			return Math.floor(parseFloat(sUnit) * bb.html.getBoxObject(oViewNode.parentNode, 'content').height / 100);
		else
			return bb.html.convertToPixels(sUnit, oViewNode);
	}

	/**
	 * Helper function to add a listener to a listeners array.
	 * @param aListeners
	 * @param oListener
	 * @private
	 */
	btl.gridBase.addListener = function(aListeners, oListener) {
		aListeners[aListeners.length] = oListener;
	}

	/**
	 * Helper function to remove a listener from a listeners array.
	 * @param aListeners
	 * @param oListener
	 * @private
	 */
	btl.gridBase.removeListener = function(aListeners, oListener) {
		for (var i = 0, len = aListeners.length; i < len; i++)
			if (aListeners[i] == oListener)
				aListeners.splice(i, 1);
	}



	/**
	 *
	 * @class Component base class.
	 * A component is a visual element of a control.
	 */
	btl.gridBase.Component = function() {
		this.viewNode = this.getTemplate();
		this.scrollListeners = [];
		this.sizeListeners = [];
	}
	btl.gridBase.Component.prototype = {};

	/**
	 * Returns a newly created HTML element representing the view
	 * @type HTMLElement
	 */
	btl.gridBase.Component.prototype.getTemplate = function() {
		return null; // implement in base classes!
	}

	/**
	 * Repaints this component
	 * @param oComponent
	 * @return The Component that was removed.
	 */
	btl.gridBase.Component.prototype.repaint = function() {
		var oNewNode = this.getTemplate();
		if (this.viewNode && this.viewNode.parentNode && this.viewNode.parentNode.nodeType == 1)
			this.viewNode.parentNode.replaceChild(oNewNode, this.viewNode);
		this.viewNode = oNewNode;
	}

	btl.gridBase.Component.prototype.setWidth = function(sWidth) {
		this.viewNode.style.width = sWidth;

		// call listeners
		for (var i = 0, len = this.sizeListeners.length; i < len; i++)
			this.sizeListeners[i].widthChanged(sWidth);
	}

	btl.gridBase.Component.prototype.setHeight = function(sHeight) {
		if (this.viewNode.style.height != sHeight) // optimisation
			this.viewNode.style.height = sHeight;

		// call listeners
		for (var i = 0, len = this.sizeListeners.length; i < len; i++)
			this.sizeListeners[i].heightChanged(sHeight);
	}

	btl.gridBase.Component.prototype.getWidth = function() {
		return this.viewNode.style.width;
	}

	btl.gridBase.Component.prototype.getHeight = function() {
		return this.viewNode.style.height;
	}

	/**
	 * Add size change listener.
	 * @return
	 */
	btl.gridBase.Component.prototype.addSizeListener = function(oSizeListener) {
		btl.gridBase.addListener(this.sizeListeners, oSizeListener);
	}

	/**
	 * Remove size change listener.
	 * @return
	 */
	btl.gridBase.Component.prototype.removeSizeListener = function(oSizeListener) {
		btl.gridBase.removeListener(this.sizeListeners, oSizeListener);
	}

	/**
	 * Scrolls to the specified position
	 */
	btl.gridBase.Component.prototype.setPosition = function(iTop) {
		if (this.viewNode.scrollTop != iTop) {
			this.viewNode.scrollTop = iTop;
			// call listeners
			for (var i = 0, len = this.scrollListeners.length; i < len; i++)
				this.scrollListeners[i].positionChanged(iTop);
		}
	}

	/**
	 * Returns the current scroll position
	 */
	btl.gridBase.Component.prototype.getPosition = function() {
		return this.viewNode.scrollTop;
	}

	/**
	 * Add scroll listener.
	 * @return
	 */
	btl.gridBase.Component.prototype.addScrollListener = function(oScrollListener) {
		btl.gridBase.addListener(this.scrollListeners, oScrollListener);
	}

	/**
	 * Remove scroll listener.
	 * @return
	 */
	btl.gridBase.Component.prototype.removeScrollListener = function(oScrollListener) {
		btl.gridBase.removeListener(this.scrollListeners, oScrollListener);
	}



	/**
	 *
	 * @class Container base class.
	 * A Component that can contain other Components.
	 */
	btl.gridBase.Container = function() {
		btl.gridBase.Component.call(this);
		this.components = [];
	}
	btl.gridBase.Container.prototype = new btl.gridBase.Component();

	/**
	 * Add a sub-component to this component
	 * @param oComponent
	 * @return The Component that was added.
	 */
	btl.gridBase.Container.prototype.add = function(oComponent) {
		this.components.push(oComponent);
		if (oComponent.viewNode)
			this.viewNode.appendChild(oComponent.viewNode);
		return oComponent;
	}

	/**
	 * Removes a sub-component to this component
	 * @param oComponent
	 * @return The Component that was removed.
	 */
	btl.gridBase.Container.prototype.remove = function(oComponent) {
		for (var i = 0, len = this.components.length; i < len; i++) {
			if (this.components[i] == oComponent) {
				this.components.splice(i, 1);
				break;
			}
		}
		if (oComponent.viewNode && oComponent.viewNode.parentNode == this.viewNode)
			this.viewNode.removeChild(oComponent.viewNode);
		return oComponent;
	}



	/**
	 *
	 * @param oController
	 * @param {String} renderMode The rendering mode, either 'DIV' or 'IFRAME'
	 */
	btl.gridBase.Grid = function(oController, sRenderMode) {
		if (oController) {
			this.controller = oController;
			btl.gridBase.Container.call(this);
			this.renderMode = sRenderMode;
			this.pollListeners = [];

			// sub-components
			this.dataContainer         = this.add(this.createDataContainer(sRenderMode));
			this.scrollers             = this.add(this.createScrollers());
			this.fridge                = this.add(this.createFridge(this.dataContainer));
			this.focusElement          = this.add(this.createFocusElement());
			this.columnInsertIndicator = this.add(this.createColumnInsertIndicator());
			this.resizeShadow          = this.add(this.createResizeShadow());
			this.resizeDummy           = this.add(this.createResizeDummy());
			this.busyIndicator         = this.add(this.createBusyIndicator());
			this.dragDummy             = this.add(this.createDragDummy());

			this.renderer = this.dataContainer.body.renderer;
			this.columns = this.createColumnList();

			this.columns.addMoveListener(this);
			this.scrollers.scrollBar.setPort(this.dataContainer.body);
			this.scrollers.scrollBar.addScrollListener(this.dataContainer.body);
			this.dataContainer.body.addSizeListener(this.scrollers.scrollBar);

			this.initialized = false; // true after it has been rendered first time and structures are initialized

			// legacy stuff...
			this.renderType = {'DIV' : Boolean(sRenderMode != 'IFRAME'), 'IFRAME' : Boolean(sRenderMode == 'IFRAME')};
			this.resize = {};
			this.aRestoreWidths = [];		// array with initial column widths
		}
	}
	btl.gridBase.Grid.prototype = new btl.gridBase.Container();

	/**
	 * Start polling.
	 */
	btl.gridBase.Grid.prototype.startPolling = function() {
			// initialise poller
			var oThis = this;
			this.poller = setTimeout(function() {
				//if not destroyed
				if (oThis.controller._) {
					for (var i = 0, len = oThis.pollListeners.length; i < len; i++)
						oThis.pollListeners[i].poll();
					oThis.poller = setTimeout(arguments.callee, 500);
				}
			}, 1000);
	}
	btl.gridBase.Grid.prototype.getTemplate = function() {
		var oDiv = document.createElement('div');
		oDiv.className = 'btl-grid-grid';
		oDiv.style.width = this.controller.getAttribute('width');
		oDiv.style.height = this.controller.getAttribute('height');
		return oDiv;
	}

	/**
	 * Add poll listener (invoked every 500ms).
	 */
	btl.gridBase.Grid.prototype.addPollListener = function(oPollListener) {
		btl.gridBase.addListener(this.pollListeners, oPollListener);
	}

	/**
	 * Remove poll listener (invoked every 500ms).
	 */
	btl.gridBase.Grid.prototype.removePollListener = function(oPollListener) {
		btl.gridBase.removeListener(this.pollListeners, oPollListener);
	}

	/**
	 * Sets the Grid's state to initialized state and fires event.
	 * @return
	 */
	btl.gridBase.Grid.prototype.setInitialized = function() {
		this.initialized = true;

		// fire initialized event
		var oEvent = bb.document.createEvent("Event");
		oEvent.initEvent("initialized", false, false);
		this.controller.dispatchEvent(oEvent);
	}

	/**
	 * Column move listener
	 */
	btl.gridBase.Grid.prototype.columnsMoved = function(iOld, iNew, iLength) {
		// update views
		this.dataContainer.header.moveColumns(iOld, iNew, iLength);
		this.dataContainer.body.moveColumns(iOld, iNew, iLength);
		this.fridge.update();
	}

	/**
	 * Factory method for a DataContainer instance
	 * @return A new DataContainer instance
	 * @type DataContainer
	 */
	btl.gridBase.Grid.prototype.createDataContainer = function(sRenderMode) {
		return new btl.gridBase.DataContainer(this, sRenderMode);
	}

	/**
	 * Factory method for a HeaderPort instance
	 * @return A new HeaderPort instance
	 * @type HeaderPort
	 */
	btl.gridBase.Grid.prototype.createHeaderPort = function(oContainer) {
		return new btl.gridBase.HeaderPort(this, oContainer);
	}

	/**
	 * Factory method for a BodyPort instance
	 * @return A new BodyPort instance
	 * @type BodyPort
	 */
	btl.gridBase.Grid.prototype.createBodyPort = function(oContainer, sRenderMode) {
		return new btl.gridBase.BodyPort(this, oContainer, sRenderMode);
	}

	/**
	 * Factory method for a Renderer instance
	 * @return A new Renderer instance
	 * @type Renderer
	 */
	btl.gridBase.Grid.prototype.createRenderer = function(oContainer, sRenderMode) {
		if (sRenderMode == 'IFRAME')
			return new btl.gridBase.IFrameRenderer(this, oContainer);
		else
			return new btl.gridBase.DivRenderer(this, oContainer);
	}

	/**
	 * Factory method for a ClonePort instance
	 * @return A new ClonePort instance
	 * @type ClonePort
	 */
	btl.gridBase.Grid.prototype.createClonePort = function(oContainer, oSourcePort) {
		return new btl.gridBase.ClonePort(this, oContainer, oSourcePort);

	}

	/**
	 * Factory method for a Scrollers instance
	 * @return A new Scrollers instance
	 * @type Scrollers
	 */
	btl.gridBase.Grid.prototype.createScrollers = function() {
		return new btl.gridBase.Scrollers(this);
	}

	/**
	 * Factory method for a VScrollBar instance
	 * @return A new VScrollBar instance
	 * @type VScrollBar
	 */
	btl.gridBase.Grid.prototype.createVScrollBar = function() {
		return new btl.gridBase.VScrollBar();
	}

	/**
	 * Factory method for a Fridge instance
	 * @return A new Fridge instance
	 * @type Fridge
	 */
	btl.gridBase.Grid.prototype.createFridge = function(oSrcContainer) {
		return new btl.gridBase.Fridge(this, oSrcContainer);
	}

	/**
	 * Factory method for a FocusElement instance
	 * @return A new FocusElement instance
	 * @type FocusElement
	 */
	btl.gridBase.Grid.prototype.createFocusElement = function() {
		return new btl.gridBase.FocusElement();
	}

	/**
	 * Factory method for a ColumnInsertIndicator instance
	 * @return A new ColumnInsertIndicator instance
	 * @type ColumnInsertIndicator
	 */
	btl.gridBase.Grid.prototype.createColumnInsertIndicator = function() {
		return new btl.gridBase.ColumnInsertIndicator();
	}

	/**
	 * Factory method for a ResizeShadow instance
	 * @return A new ResizeShadow instance
	 * @type ResizeShadow
	 */
	btl.gridBase.Grid.prototype.createResizeShadow = function() {
		return new btl.gridBase.ResizeShadow();
	}

	/**
	 * Factory method for a ResizeDummy instance
	 * @return A new ResizeDummy instance
	 * @type ResizeDummy
	 */
	btl.gridBase.Grid.prototype.createResizeDummy = function() {
		return new btl.gridBase.ResizeDummy();
	}

	/**
	 * Factory method for a BusyIndicator instance
	 * @return A new BusyIndicator instance
	 * @type BusyIndicator
	 */
	btl.gridBase.Grid.prototype.createBusyIndicator = function() {
		return new btl.gridBase.BusyIndicator(this);
	}

	/**
	 * Factory method for a DragDummy instance
	 * @return A new DragDummy instance
	 * @type DragDummy
	 */
	btl.gridBase.Grid.prototype.createDragDummy = function() {
		return new btl.gridBase.DragDummy();
	}

	/**
	 * Factory method for a ColumnList instance
	 * @return A new ColumnList instance
	 * @type ColumnList
	 */
	btl.gridBase.Grid.prototype.createColumnList = function() {
		return new btl.gridBase.ColumnList(this);
	}

	/**
	 * Factory method
	 */
	btl.gridBase.Grid.prototype.createColumn = function(iIndex, oViewNode) {
		return new btl.gridBase.Column(this, iIndex, oViewNode);
	}

	// A set of class names
	// TODO: decide whether this is a semi-instance variable or a static variable
	btl.gridBase.hClassNames =
	btl.gridBase.Grid.prototype.hClassNames = {
		'view' : 'btl-grid-port',
		'table' : 'btl-grid-table',
		'row' : 'btl-grid-row',
		'cell' : 'btl-grid-data',
		'header' : 'btl-grid-header', /* header cell */
		'headerTable' : 'btl-grid-header-table', /* header table */
		'columnSizerRow' : 'btl-grid-clear',
		'no_resize' : 'btl-grid-noresize'
	};
	// TODO: decide whether this is a semi-instance variable or a static variable
	btl.gridBase.hSelectors =
	btl.gridBase.Grid.prototype.hSelectors = {
		'view' : 'DIV.btl-grid-port',
		'table' : 'TABLE.btl-grid-table',
		'row' : 'TR.btl-grid-row',
		'cell' : 'td.btl-grid-data',
		'header' : 'td.btl-grid-header', /* header cell */
		'columnSizerRow' : 'TR.btl-grid-clear'
	};

	/**
	 * Finds the Port belonging to the node
	 */
	btl.gridBase.Grid.prototype.findPort = function(oNode) {
		while (oNode && !oNode.bb_portObject)
			oNode = oNode.parentNode;
		if (!oNode)
			return null;
		return oNode.bb_portObject;
	}

	/**
	 * Returns a TableIndex object containing the location of the node passed.
	 * @param {Object} node Node to get the index for (TD, TH, TR or TABLE).
	 * @return TableIndex instance containing node index.
	 * @type TableIndex
	 */
	btl.gridBase.Grid.prototype.getTableIndex = function(oNode) {
		oPort = this.findPort(oNode);
		return oPort ? oPort.getTableIndex(oNode) : null;
	}

	/**
	 * Returns left limit for resizing and drag-and-drop
	 * it depends if there are visible frozen columns or not,
	 * if a column belongs to frozen columns or not
	 * @param {Number} column  Column index
	 * @param {Boolean} client  Calculate offset in client area, otherwise it's global
	 * @return
	 * @type Number
	 */
	btl.gridBase.Grid.prototype.getLeftLimit = function(iColumn, bClient) {
		//set left border limit
		var iLeftLimit;
		var oGridBox = bb.html.getBoxObject(this.viewNode, 'content');
		if (!this.fridge.isEmpty() && !this.fridge.isFrozen(iColumn)) {
			var oFBox = bb.html.getBoxObject(this.fridge.viewNode);
			iLeftLimit = oFBox.left + oFBox.width;
		} else
			iLeftLimit = oGridBox.left;

		if (bClient)
			iLeftLimit -= oGridBox.left;

		return iLeftLimit;
	};

	/**
	 * Sets grid width, height and handle scrollbars
	 */
	btl.gridBase.Grid.prototype.doLayout = function() {
		var oContainer = this.dataContainer;
		if (!oContainer.body.tables) return;
		
		var oHeader = this.dataContainer.header;
		var oBody = this.dataContainer.body;
		
		var sWidth = this.controller.getProperty('width');
		var sHeight = this.controller.getProperty('height');
		var bAutoWidth = sWidth == 'auto';
		var bAutoHeight = sHeight == 'auto';
		var bIsBorderBox = bb.html.getStyle(this.viewNode, 'box-sizing') == 'border-box';

		var oGridBorders = btl.gridBase.getBorderSizes(this.viewNode);
		var iScrollBarSize = bb.html.getScrollBarWidth();
		var iContentWidth = this.dataContainer.getContentWidth();

		// correction due to visibility/invisibility of vertical scrollbar and box-sizing
		var iWidthCorrection = bIsBorderBox ? oGridBorders.leftright : 0;
		//hide table right border with the grid border, does not work in ie
		if (!bb.browser.ie)
			iWidthCorrection--;

		// converting width value to pixels and calculating proper content width
		var iWidth;
		if (bAutoWidth) {
			iWidth = iContentWidth + iWidthCorrection;
		} else {
			iWidth = btl.gridBase.unitToPixelWidth(sWidth, this.viewNode);
		}
		// checking if the width is not too small
		// XXX: Why? It seems kind of arbitrary...
		iWidth = Math.max(iWidth, iScrollBarSize);

		var iContainerWidth = iWidth - iWidthCorrection;

		// setting whole container's width
		this.setWidth(iWidth + 'px');
		// setting width of the container (header and body)
		oContainer.setWidth(iContainerWidth + "px");

		var sPortWidth = iContentWidth <= iWidth ? 'auto' : iContentWidth + 'px';
		oHeader.setWidth(sPortWidth);
		oBody.setWidth(sPortWidth);

		var bHScrollBar = !bAutoWidth && oContainer.hasHScrollBar();

		//------------- set height -----------------
		var iHeaderHeight = this.dataContainer.header.getContentHeight();

		var iHeightCorrection = bHScrollBar ? iScrollBarSize : 0;
		if (bIsBorderBox) {
			iHeightCorrection += oGridBorders.topbottom;
		}
		if (bb.browser.ie) {
			oContainer.viewNode.style.paddingBottom = (bHScrollBar ? iScrollBarSize : 0) + "px";
		}

		// converting the height value to pixels if needed
		var iHeight;
		if (bAutoHeight) {
			iHeight = this.dataContainer.getContentHeight() + iHeightCorrection;
		} else {
			iHeight = btl.gridBase.unitToPixelHeight(sHeight, this.viewNode);
		}
		// calculating height for the inner container (excludes header height and top and bottom borders)
		var iInnerHeight = iHeight - (iHeaderHeight + iHeightCorrection);

		/* IE will throw errors when setting width/height to a negative value so we have to correct the value. */
		if (iInnerHeight < 0) iInnerHeight = 0;

		// setting the body height
		oBody.setHeight(bAutoHeight && !bb.browser.ie ? "auto" : iInnerHeight + "px");

		// setting whole container to auto
		if (!this.initialized)
			this.setHeight("auto");

		//---------handle vertical scrollbar - update width
		var bVScrollBar = oContainer.hasVScrollBar();
		if (bVScrollBar) {
			if (bAutoWidth)
				this.setWidth(iWidth + iScrollBarSize + (bIsBorderBox ? 0 : 1 ) + "px");//width without scrollbar + scrollbar + scrollbarholder border
			else { //update container
				var iWidth2Set = (iContainerWidth - iScrollBarSize - (bIsBorderBox ? 0 : 1 ));
				if (iWidth2Set <= 0) iWidth2Set = iScrollBarSize;//bug 11666				
				oContainer.setWidth(iWidth2Set + "px");
			}
		}
		//show or hide vertical scrollbar
		if (bVScrollBar)
			this.scrollers.show();
		else
			this.scrollers.hide();

		this.scrollers.setHeight(bb.html.getBoxObject(this.viewNode, 'content').height + 'px');
		this.scrollers.scrollBar.updateScrollHeight();

		// widening the last column if needed
		if (iContentWidth < iWidth && this.columns.widthsInitialized) {
			this.columns.fillFreeColumn();
		}

		//update frozen area size
		this.fridge.updateWidth();

		//notify about changed dimensions
		if (this.initialized)
			bb.command.fireEvent(this.controller, 'layoutUpdated', false, false);
	}

	btl.gridBase.Grid.prototype.setWidth = function(sWidth) {
		this.viewNode.style.width = sWidth;
	}

	btl.gridBase.Grid.prototype.setHeight = function(sHeight) {
		this.viewNode.style.height = sHeight;
	}



	/**
	 * @param sRenderMode
	 * @class XXX: == anonymous oViews on oGrid
	 */
	btl.gridBase.DataContainer = function(oGrid, sRenderMode) {
		btl.gridBase.Container.call(this);
		this.grid = oGrid;
		this.header = this.add(this.grid.createHeaderPort(this));
		this.body = this.add(this.grid.createBodyPort(this, sRenderMode));
		this.header.viewNode.style.visibility = '';
		this.body.viewNode.style.visibility = 'hidden';

		//listen to header height changes and update data area size
		this.headerHeight = this.header.getContentHeight();
		oGrid.addPollListener(this);
	}
	btl.gridBase.DataContainer.prototype = new btl.gridBase.Container();

	btl.gridBase.DataContainer.prototype.getTemplate = function() {
		var oDiv = document.createElement('div');
		oDiv.className = 'btl-grid-data-container';
		return oDiv;
	}

	btl.gridBase.DataContainer.prototype.poll = function() {
		if (this.header.getContentHeight() != this.headerHeight) {
			this.grid.doLayout();
			this.headerHeight = this.header.getContentHeight();
		}
	}

	btl.gridBase.DataContainer.prototype.getContentWidth = function() {
		return this.body.getContentWidth();
	}

	btl.gridBase.DataContainer.prototype.getContentHeight = function() {
		return this.header.getContentHeight() + this.body.getContentHeight();
	}

	/**
	 * Checks if the container has a horizontal scrollbar
	 */
	btl.gridBase.DataContainer.prototype.hasHScrollBar = function() {
		if (bb.browser.opera)
			return this.viewNode.scrollWidth > this.viewNode.offsetWidth;
		return this.viewNode.scrollWidth > this.viewNode.clientWidth;
	}

	btl.gridBase.DataContainer.prototype.hasVScrollBar = function() {
		return this.body.hasVScrollBar();
	}


	/**
	 * @class Container for frozen columns
	 */
	btl.gridBase.Fridge = function(oGrid, oSrcContainer) {
		if (oGrid) {
			btl.gridBase.Container.call(this);
			this.grid = oGrid;
			this.sourceContainer = oSrcContainer;
			this.count = 0; //number of frozen columns
			// size change polling
			this.curHeight = this.viewNode.offsetHeight;
			this.curWidth = this.viewNode.offsetWidth;
			this.grid.addPollListener(this);
		}
	}
	btl.gridBase.Fridge.prototype = new btl.gridBase.Container();

	btl.gridBase.Fridge.prototype.getTemplate = function() {
		var oDiv = document.createElement('div');
		oDiv.className = 'btl-grid-fridge';
		oDiv.style.display = 'none';
		return oDiv;
	}

	/**
	 * Poll for size changes
	 */
	btl.gridBase.Fridge.prototype.poll = function() {
		if (this.curHeight != this.viewNode.offsetHeight) {
			this.curHeight = this.viewNode.offsetHeight;
			// call listeners
			for (var i = 0, len = this.sizeListeners.length; i < len; i++)
				this.sizeListeners[i].heightChanged(this.curHeight + 'px');
		}
		if (this.curWidth != this.viewNode.offsetWidth) {
			this.curWidth = this.viewNode.offsetWidth;
			// call listeners
			for (var i = 0, len = this.sizeListeners.length; i < len; i++)
				this.sizeListeners[i].widthChanged(this.curWidth + 'px');
		}
	}

	btl.gridBase.Fridge.prototype.isEmpty = function() {
		return !this.header && !this.body;
	}

	/**
	 * Freezes columns
	 * @param {Number} count Number of first columns to freeze (optional)
	 * If count is not passed then it will refresh the frozen area size
	 */
	btl.gridBase.Fridge.prototype.freeze = function(iCount) {
		if (iCount == 0) {
			this.unfreeze();
			return;
		}
		this.grid.busyIndicator.addBusyState();

		// TODO: Create factory methods
		if (this.isEmpty()) {
			// create clone ports if they don't exist yet
			this.header = this.add(this.grid.createClonePort(this, this.sourceContainer.header));
			this.body = this.add(this.grid.createClonePort(this, this.sourceContainer.body));
		}

		// TODO: add check if iCount > total number of columns
		this.count = iCount;
		this.updateWidth();
		this.viewNode.scrollLeft = 0; //FF fix

		this.grid.busyIndicator.removeBusyState();
	}

	/**
	 * XXX: what should the name be; update, refresh, repaint?
	 * @return
	 */
	btl.gridBase.Fridge.prototype.update = function() {
		if (!this.isEmpty()) {
			this.grid.busyIndicator.addBusyState();
			this.header.repaint();
			this.body.repaint();
			this.updateWidth();
			this.updatePosition();
			this.grid.busyIndicator.removeBusyState();
		}
	}

	/**
	 * Destroys frozen columns
	 */
	btl.gridBase.Fridge.prototype.unfreeze = function() {
		this.viewNode.style.display = 'none';
		this.remove(this.header).destructor();
		this.remove(this.body).destructor();
		this.header = null;
		this.body = null;
		this.count = 0;
	};

	/**
	 * Adjust fridge width to only overlay the number of columns specified
	 * @return
	 */
	btl.gridBase.Fridge.prototype.updateWidth = function() {
		if (this.isEmpty()) return;
		var oCell = this.grid.columns.getColumn(this.count - 1).viewNode;
		if (oCell) { //avoid wrong count
			iWidth = oCell.offsetLeft + oCell.clientWidth;
			if (iWidth > this.sourceContainer.viewNode.offsetWidth) {
				iWidth = this.sourceContainer.viewNode.offsetWidth;
			}
			this.viewNode.style.width = iWidth + 'px';
			this.viewNode.style.display = 'block';
		}
	}

	/**
	 * Adjust fridge position
	 * @return
	 */
	btl.gridBase.Fridge.prototype.updatePosition = function() {
		this.body.viewNode.scrollTop = this.sourceContainer.body.getPosition();
	}

	/**
	 * Returns whether a column is frozen
	 * @param {Integer} column Column index
	 * @return <code>true</code> if it is frozen, <code>false</code> if not
	 */
	btl.gridBase.Fridge.prototype.isFrozen = function(iColumn) {
		return iColumn < this.count;
	}



	/**
	 *
	 * @class A viewport to grid data
	 * XXX: Rename to GridView instead of Port?
	 */
	btl.gridBase.Port = function(oGrid, oContainer) {
		if (oGrid) {
			btl.gridBase.Component.call(this);
			this.grid = oGrid;
			this.container = oContainer;
			this.changeListeners = [];
			this.tables = null;
			this.rangesList = null;
			this.ranges = null;
		}
	}
	btl.gridBase.Port.prototype = new btl.gridBase.Component();

	btl.gridBase.Port.prototype.getTemplate = function() {
		var oDiv = document.createElement('div');
		oDiv.className = 'btl-grid-port';
		oDiv.bb_portObject = this;
		return oDiv;
	}

	/**
	 * Add table change listener.
	 * @return
	 */
	btl.gridBase.Port.prototype.addChangeListener = function(oChangeListener) {
		btl.gridBase.addListener(this.changeListeners, oChangeListener);
	}

	/**
	 * Remove table change listener.
	 * @return
	 */
	btl.gridBase.Port.prototype.removeChangeListener = function(oChangeListener) {
		btl.gridBase.removeListener(this.changeListeners, oChangeListener);
	}

	btl.gridBase.Port.prototype.hasVScrollBar = function() {
		return btl.gridBase.getScrollHeight(this.viewNode) > this.viewNode.clientHeight && this.viewNode.clientHeight >= bb.html.getScrollBarWidth();
	}

	/**
	 * Returns the row top offset relative to the containing Port.
	 * @param oRow The row
	 * @return Row top offset relative to the containing Port.
	 */
	btl.gridBase.Port.prototype.getRowOffset = function(oRow) {
		return oRow.offsetParent.offsetTop + oRow.offsetTop;
	}

	/**
	 * Scrolls the item into view by changing the body area position
	 */
	btl.gridBase.Port.prototype.scrollIntoView = function(oNode, bVerticalOnly) {
		var oScrollBar = this.grid.scrollers.scrollBar;
		if (bb.html.hasClass(oNode, 'btl-grid-row')) {
			// if it is a grid row, just scroll vertically
			var iTop = this.getRowOffset(oNode),
				iContainerTop = this.getPosition();
				iBottom = iTop + oNode.offsetHeight;
				iContainerBottom = iContainerTop + this.viewNode.offsetHeight;
			if (iTop < iContainerTop)
				oScrollBar.setPosition(iTop);
			else if (iBottom > iContainerBottom)
				oScrollBar.setPosition(Math.max(iTop - this.viewNode.offsetHeight + oNode.offsetHeight, 0));
		} else {
			bb.html.scrollIntoView(oNode);
			oScrollBar.setPosition(this.getPosition());
		}
	}

	btl.gridBase.Port.prototype.getContentWidth = function() {
		return bb.html.getBoxObject(this.getTable(0)).width;
	}

	btl.gridBase.Port.prototype.getContentHeight = function() {
		return btl.gridBase.getScrollHeight(this.viewNode);
	}

	/**
	 * Position change listener
	 * @param iTop
	 */
	btl.gridBase.Port.prototype.positionChanged = function(iTop) {
		this.setPosition(iTop);
	}

	/**
	 * Updates and returns view tables
	 * XXX: Updates? Ah, it means updates the aTables property.
	 */
	btl.gridBase.Port.prototype.refreshTables = function() {
		this.tables = bb.selector.queryAll(this.viewNode, btl.gridBase.hSelectors.table);
		return this.tables;
	}

	/**
	 * Returns a grid cell based on table-, row- and cell-index.
	 * XXX: it throws if aTables doesn't exist...
	 * @param iTable Table number
	 * @param iRow Row number
	 * @param iCell Cell number
	 * @return Requested table cell
	 * @type HTMLTableElement
	 */
	btl.gridBase.Port.prototype.getTable = function(iTable) {
		return this.tables ? this.tables[iTable] || null : null;
	}

	/**
	 * Returns a grid cell based on table-, row- and cell-index.
	 * XXX: it throws if table doesn't exist...
	 * @param iTable Table number
	 * @param iRow Row number
	 * @param iCell Cell number
	 * @return Requested table cell
	 * @type HTMLTableRowElement
	 */
	btl.gridBase.Port.prototype.getTableRow = function(iTable, iRow) {
		var oTable = this.tables[iTable];
		return oTable && oTable.rows[iRow] || null;
	}

	/**
	 * Returns a grid cell based on table-, row- and cell-index.
	 * XXX: it throws if table or row doesn't exist...
	 * @param iTable Table number
	 * @param iRow Row number
	 * @param iCell Cell number
	 * @return Requested table cell
	 * @type HTMLTableCellElement
	 */
	btl.gridBase.Port.prototype.getTableCell = function(iTable, iRow, iCell) {
		var oTable = this.tables[iTable];
		var oRow = oTable && oTable.rows[iRow] || null;
		return oRow && oRow.cells[iCell] || null;
	}

	/**
	 * Gets an array containing all table rows.
	 * @param aRows Array to append rows to (optional)
	 * @return
	 */
	btl.gridBase.Port.prototype.getAllRows = function(aRows) {
		if (!aRows)
			aRows = [];
		for (var i = 0, iMax = this.tables.length; i < iMax; i++) {
			var oTable = this.tables[i];
			for (var j = 0, jLen = oTable.rows.length; j < jLen; j++) {
				aRows[aRows.length] = oTable.rows[j];
			}
		}
		return aRows;
	}

	/**
	 * Creates column indexes for every table to support row/col spanning
	 * XXX: move this to Port!
	 * @param {Port} The port containing the header cells
	 */
	btl.gridBase.Port.prototype.refreshRanges = function() {
		this.rangesList = [];
		for (var i = 0, len = this.tables.length; i < len; i++)
			this.rangesList[i] = btl.gridBase.computeCellRanges(this.tables[i]);
		this.ranges = this.rangesList[0];
	}

	/**
	 * Calculates header cell column indexes
	 * @param {HTMLTableCellElement} cell A header cell to get indexes
	 * @return An object { 'first' : iFirstIndex, 'last' : iLastIndex }. First and last are inclusive.
	 */
	btl.gridBase.Port.prototype.getRange = function(oCell) {
		if (!oCell || oCell.nodeType != 1 || (oCell.tagName.toUpperCase() != 'TD' && oCell.tagName.toUpperCase() != 'TH'))
			return null;
		if (bb.html.hasClass(oCell, btl.gridBase.hClassNames.header))
			return this.ranges[oCell.parentNode.rowIndex][oCell.cellIndex];
		return new btl.gridBase.Range(oCell.cellIndex, oCell.cellIndex);
	}

	btl.gridBase.Port.prototype.getRangeByIndex = function(iRowIndex, iCellIndex) {
		return this.ranges[iRowIndex][iCellIndex];
	}

	/**
	 * Returns a TableIndex object containing the location of the node passed.
	 * @param {Object} node Node to get the index for (TD, TH, TR or TABLE).
	 * @return TableIndex instance containing node index.
	 * @type TableIndex
	 */
	btl.gridBase.Port.prototype.getTableIndex = function(oNode) {
		var iTable, iRow, iCell;
		while (oNode) {
			switch (oNode.nodeType == 1 && oNode.tagName.toUpperCase()) {
				case 'TD':
				case 'TH':
					iCell = this.getRange(oNode).first;
					break;
				case 'TR':
					iRow = oNode.rowIndex;
					break;
				case 'TABLE':
					var aTables = this.tables;
					for (var i = 0, len = aTables.length; i < len; i++)
						if (aTables[i] == oNode)
							return new btl.gridBase.TableIndex(this, i, iRow, iCell);
			}
			oNode = oNode.parentNode;
		}
		return null;
	}

	btl.gridBase.Port.prototype.getSizerRow = function(iTable) {
		return this.tables[iTable].rows[0];
	}

	btl.gridBase.Port.prototype.getTableCount = function() {
		return this.tables.length;
	}

	btl.gridBase.Port.prototype.getRowCount = function() {
		var iRows = 0;
		for (var i = 0, len = this.tables.length; i < len; i++)
			iRows += this.tables[i].rows.length;
		return iRows;
	}

	btl.gridBase.Port.prototype.hasRows = function() {
		return this.tables[0].rows.length > 1;
	}

	/**
	 * Remove a row
	 * @param oTableIndex A TableIndex object indicating which row to remove
	 */
	btl.gridBase.Port.prototype.removeRow = function(oTableIndex) {
		var oRow = oTableIndex.getElementFromPort(this);
		oRow.parentNode.removeChild(oRow);

		// if table is empty now (except for sizer), remove it and update aTables
		var oTable = this.tables[oTableIndex.table];
		if (oTable.rows.length <= 1 && this.tables.length > 1) {
			oTable.parentNode.removeChild(oTable);
			this.refreshTables();
		}

		// call listeners
		for (var i = 0, len = this.changeListeners.length; i < len; i++)
			this.changeListeners[i].rowRemoved(oTableIndex);

		return oRow;
	}

	/**
	 * Removes all rows from the table.
	 * (It just leaves one table with a sizer row).
	 */
	btl.gridBase.Port.prototype.clear = function() {
		var aTables = this.tables;
		//remove all rows except the sizer in the first table
		var oBody = aTables[0].rows[0].parentNode;
		for (var i = aTables[0].rows.length - 1; i > 0; i--)
			oBody.removeChild(aTables[0].rows[i]);
		//remove all tables except the first
		for (var t = 1; t < aTables.length; t++)
			aTables[t].parentNode.removeChild( aTables[t]);
		this.refreshTables();

		// call listeners
		for (var i = 0, len = this.changeListeners.length; i < len; i++)
			this.changeListeners[i].portCleared();
	}

	/**
	 * Signal to listeners that a certain row has updated.
	 */
	btl.gridBase.Port.prototype.signalRowUpdated = function(oTableIndex) {
		// call listeners
		for (var i = 0, len = this.changeListeners.length; i < len; i++)
			this.changeListeners[i].rowUpdated(oTableIndex);
	}

	/**
	 * Move columns
	 * @param iOld Old column index
	 * @param iNew New column index
	 * @param iLength Number of columns to move (optional)
	 * @return
	 */
	btl.gridBase.Port.prototype.moveColumns = function(iOld, iNew, iLength) {
		if (iLength == undefined)
			iLength = 1;
		// XXX idea: work with a documentFragment!
		var iOldEnd = iOld + iLength;
		var aRows = this.getAllRows();
		for (var r = 0; r < aRows.length; r++) {
			var oRow = aRows[r];
			// XXX: maybe this bounds checking can move up outside the loop (performance)?
			var oCell = iNew < oRow.cells.length ? oRow.cells[iNew] : null;
			// because cells change their indexes
			var aCells = [];
			for (var c = iOld; c < iOldEnd; c++)
				aCells.push(oRow.cells[c]);
			for (var i = 0; i < aCells.length; i++)
				oRow.insertBefore(aCells[i], oCell);
		}
	}

	btl.gridBase.Port.prototype.setColumnWidth = function(iIndex, sWidth) {
		var aTables = this.tables;
		for (var i = 0; i < aTables.length; i++) {
			aCells = aTables[i].rows[0].cells;
			if (aCells.length > iIndex)
				aCells[iIndex].style.width = sWidth;
		}
	}

	/**
	 * Returns view rows
	 * XXX: Broken? This doesn’t seem to do anything if this.aRows is undefined...
	 */
	btl.gridBase.Port.prototype.getRows = function() {
		if (!this.aRows)
			bb.selector.queryAll(this.viewNode, btl.gridBase.hSelectors.row);
		return this.aRows;
	}

	/**
	 * Returns view elements
	 * XXX: Seems rather pointless... at least improve the description
	 */
	btl.gridBase.Port.prototype.getElements = function(sSelector) {
		 return bb.selector.queryAll(this.viewNode, sSelector);
	}



	/**
	 *
	 * @class A clone of another Port
	 */
	btl.gridBase.ClonePort = function(oGrid, oContainer, oSrcPort) {
		this.sourcePort = oSrcPort;
		btl.gridBase.Port.call(this, oGrid, oContainer);
		this.refreshTables();

		// add listeners
		oSrcPort.addChangeListener(this);
		oSrcPort.addSizeListener(this);
		oSrcPort.addScrollListener(this);
	}
	btl.gridBase.ClonePort.prototype = new btl.gridBase.Port();

	btl.gridBase.ClonePort.prototype.destructor = function() {
		// remove listeners
		this.sourcePort.removeChangeListener(this);
		this.sourcePort.removeSizeListener(this);
		this.sourcePort.removeScrollListener(this);
	}

	btl.gridBase.ClonePort.prototype.getTemplate = function() {
		var oClone = this.sourcePort.viewNode.cloneNode(true);
		oClone.bb_portObject = this;
		return oClone;
	}

	/**
	 * Copies the entire source port
	 */
	btl.gridBase.ClonePort.prototype.repaint = function() {
		btl.gridBase.Port.prototype.repaint.call(this);
		this.refreshTables();
	}

	/**
	 * Copies a row from the source port
	 */
	btl.gridBase.ClonePort.prototype.repaintRow = function(oTableIndex) {
		var oOldRow = oTableIndex.getElementFromPort(this);
		var oNewRow = oTableIndex.getElementFromPort(this.sourcePort).cloneNode(true);
		oOldRow.parentNode.replaceChild(oNewRow, oOldRow);
	}

	/**
	 * Row removal listener
	 * @param oTableIndex
	 */
	btl.gridBase.ClonePort.prototype.rowRemoved = function(oTableIndex) {
		this.removeRow(oTableIndex);
	}

	/**
	 * Row update listener
	 * @param oTableIndex
	 */
	btl.gridBase.ClonePort.prototype.rowUpdated = function(oTableIndex) {
		this.repaintRow(oTableIndex);
	}

	/**
	 * Port clearing listener
	 */
	btl.gridBase.ClonePort.prototype.portCleared = function() {
		this.clear();
	}

	/**
	 * Width change listener
	 */
	btl.gridBase.ClonePort.prototype.widthChanged = function(sWidth) {
	}

	/**
	 * Height change listener
	 */
	btl.gridBase.ClonePort.prototype.heightChanged = function(sHeight) {
		this.setHeight(sHeight);
	}


	/**
	 *
	 * @class A header viewport
	 */
	btl.gridBase.HeaderPort = function(oGrid, oContainer) {
		btl.gridBase.Port.call(this, oGrid, oContainer);
		this.writeData = null;
	}
	btl.gridBase.HeaderPort.prototype = new btl.gridBase.Port();

	btl.gridBase.HeaderPort.prototype.setColumnWidth = function(iIndex, sWidth) {
		var aTables = this.tables;
		var bFix11652 = bb.browser.ie || (bb.browser.gecko && bb.browser.version == 1.8);

		for (var i = 0; i < aTables.length; i++) {
			aCells = aTables[i].rows[0].cells;
			if (aCells.length > iIndex) {
				aCells[iIndex].style.width = sWidth;
				if ( bFix11652) {//bug 11652
					var sHidden = parseInt(sWidth) == 0 ? 'hidden' : '';
					var arr = this.rangesList[i];
					for(var r = 0; r < arr.length; r++)
						for(var j = 0, jMax = Math.min(iIndex+1, arr[r].length); j < jMax; j++) {
							var index = arr[r][j];
							if (index.first == index.last && index.first == iIndex){ //one cell
								aTables[i].rows[r].cells[j].style.visibility = sHidden;
							}
						}
				}//if IE		
			} //if
		}//for aTables
	}

	btl.gridBase.HeaderPort.prototype.dataReady = function(oDiv, iNCols) {
		// XXX: ?? - FF2 it does not make a horizontal scroll bar
		var sNbsp = bb.browser.gecko ? String.fromCharCode(160) : '';

		// copy HTML into header
		var cur = null;
		for(var i = oDiv.childNodes.length - 1; i >= 0; i--)
			cur = this.viewNode.insertBefore( oDiv.childNodes[i], cur);

		this.refreshTables();
		if (!this.tables.length) {
			bb.command.trace(this.grid.controller, 'No header table is found', 3);
			return;
		}
		for (var i=0, iMax = this.tables.length; iMax > i; i++)	{
			var eTable = this.tables[i];
			var oDocument = eTable.ownerDocument;

			// set the table class name
			bb.html.addClass(eTable, [btl.gridBase.hClassNames.table, btl.gridBase.hClassNames.headerTable]);
			if (!eTable.rows.length || !bb.html.hasClass(eTable.rows[0], btl.gridBase.hClassNames.columnSizerRow)) {
				//create the first empty hidden row
				var oRow = oDocument.createElement('tr');
				oRow.className = btl.gridBase.hClassNames.columnSizerRow;
				for (var j=0; j < iNCols; j++)
					oRow.appendChild(oDocument.createElement('td')).appendChild(oDocument.createTextNode(sNbsp));

				var oBody = eTable.rows.length ? eTable.rows[0].parentNode :
								(eTable.tBodies.length ? eTable.tBodies[0] :	eTable.appendChild(oDocument.createElement('tbody')));
				oBody.insertBefore(oRow, oBody.rows[0] ? oBody.rows[0] : null);
			}
		}
		this.grid.columns.initialize(this);
	}

	// @Override
	btl.gridBase.HeaderPort.prototype.moveColumns = function(iOld, iNew, iLength) {
		if (iLength == undefined)
			iLength = 1;

		var iOldEnd = iOld + iLength;
		var oTable = this.tables[0];
		//optimization - no need to check cells after
		var iMaxCell = Math.max(iOldEnd, iNew + 1);

		for (var r = 0, rMax = oTable.rows.length; r < rMax; r++) {
			var oRow = oTable.rows[r];
			var oTarget = null;//to append a child
			var aCells = [];
			for (var c = 0, cMax = oRow.cells.length; c < cMax; c++) {
				var oRange = this.getRangeByIndex(r, c);
				if (!oTarget && oRange.first >= iNew)
					// target found
					oTarget = oRow.cells[c];
				//select cells to move
				if (oRange.first >= iOld && oRange.last < iOldEnd)
					aCells.push(oRow.cells[c]);
				else if (oRange.last >= iMaxCell)
					break;
			}
			if (aCells.length && (
					(iNew > iOld && oRow.cells[aCells[aCells.length - 1].cellIndex + 1] != oTarget) || //move to the right
					(iNew < iOld && aCells[0] != oTarget) //move to the left
				)) {
				for (var i = 0; i < aCells.length; i++) {
					oRow.insertBefore(aCells[i], oTarget);
				}
			}
		}
	}

	/**
	 * Returns array of header cells acceptable as drag-and-drop targets
	 * @param {Number} headerCell A header cell (can cover several grid columns)
	 * @return
	 * @type Array
	 */
	btl.gridBase.HeaderPort.prototype.getDropTargets = function(eHeaderCell) {
		var aHCells = [];
		var eRow = eHeaderCell.parentNode;
		var iRow = eRow.rowIndex;
		//find acceptable targets
		while (!aHCells.length && iRow >= 1) {//this loop need for rowspanned headers
			iRow--;
			if (iRow > 0) {
				var oSourceIndex = this.getRange(eHeaderCell);
				var aCells = eRow.parentNode.parentNode.rows[iRow].cells;
				var oIndex = null;
				for (var i = 0, iMax = aCells.length; i < iMax; i++) {
					oIndex = this.getRange(aCells[i]);
					if (oIndex.last >= oSourceIndex.last && oIndex.first <= oSourceIndex.first) {//found
						for (var eCur = eHeaderCell.previousSibling; eCur; eCur = eCur.previousSibling) {
							if (eCur.nodeType == 1) {
								var oCurIndex = this.getRange(eCur);
								if (oCurIndex.first >= oIndex.first)
									aHCells.push(eCur);
							}
						}
						for (var eCur = eHeaderCell.nextSibling; eCur; eCur = eCur.nextSibling) {
							if (eCur.nodeType == 1) {
								var oCurIndex = this.getRange(eCur);
								if (oCurIndex.last <= oIndex.last)
									aHCells.push( eCur);
							}
						}
						break;
					}
				}//for
			} else { // top level - no parents
				for (var eCur = eHeaderCell.previousSibling; eCur; eCur = eCur.previousSibling)
					if (eCur.nodeType == 1)
						aHCells.push(eCur);
				for (var eCur = eHeaderCell.nextSibling; eCur; eCur = eCur.nextSibling)
					if (eCur.nodeType == 1)
						aHCells.push(eCur);
			}
		}
		return aHCells;
	}



	/**
	 *
	 * @class A viewport to body data
	 */
	btl.gridBase.BodyPort = function(oGrid, oContainer, sRenderMode) {
		btl.gridBase.Port.call(this, oGrid, oContainer);
		this.renderer = this.grid.createRenderer(this, sRenderMode);
	}
	btl.gridBase.BodyPort.prototype = new btl.gridBase.Port();

	btl.gridBase.BodyPort.prototype.dataReady = function(oDiv, iNCols) {
		// XXX: ?? - FF2 it does not make a horizontal scroll bar
		var sNbsp = bb.browser.gecko ? String.fromCharCode(160) : '';
		var oDocument = oDiv.ownerDocument;

		this.refreshTables(true);
		//to be sure that a container sized correctly
		oDiv.style.width = 'auto';
		oDiv.style.height = 'auto';

		if (!this.tables.length) {
			var aTable = oDiv.appendChild(oDocument.createElement('table'));
			aTable.className = btl.gridBase.hClassNames.table;
			this.tables.push( aTable);
		}
		for (var i=0, iMax = this.tables.length; iMax > i; i++)	{
			var eTable = this.tables[i];
			if (!eTable.rows.length || !bb.html.hasClass(eTable.rows[0], btl.gridBase.hClassNames.columnSizerRow)) {
				//create the sizer row
				var oRow = oDocument.createElement('tr');
				oRow.className = btl.gridBase.hClassNames.columnSizerRow;
				for (var j=0; j < iNCols; j++)
					oRow.appendChild(oDocument.createElement('td')).appendChild(oDocument.createTextNode(sNbsp));

				var oBody = eTable.rows.length ? eTable.rows[0].parentNode :
								(eTable.tBodies.length ? eTable.tBodies[0] : eTable.appendChild(oDocument.createElement('tbody')));
				oBody.insertBefore(oRow, oBody.rows[0] ? oBody.rows[0] : null);
			}
		}

		// Position the grid
		this.grid.columns.initWidth();
		this.grid.doLayout();

		this.viewNode.style.visibility = '';

		var iFrozen = parseInt(this.grid.controller.getAttribute('frozenColumns'));
		if (iFrozen)
			this.grid.fridge.freeze(iFrozen);
	}

	btl.gridBase.BodyPort.prototype.getContentHeight = function() {
		return this.renderer.getContentHeight();
	}



	/**
	 *
	 * @param oGrid
	 * @param oDataPort
	 */
	btl.gridBase.Renderer = function(oGrid, oDataPort) {
		if (oGrid) {
			btl.gridBase.Component.call(this);
			this.grid = oGrid;
			this.port = oDataPort;
			this.renderListeners = [];
		}
	}
	btl.gridBase.Renderer.prototype = new btl.gridBase.Component();

	/**
	 * Add table change listener.
	 * @return
	 */
	btl.gridBase.Renderer.prototype.addChangeListener = function(oRenderListener) {
		btl.gridBase.addListener(this.renderListeners, oRenderListener);
	}

	/**
	 * Remove table change listener.
	 * @return
	 */
	btl.gridBase.Renderer.prototype.removeChangeListener = function(oRenderListener) {
		btl.gridBase.removeListener(this.renderListeners, oRenderListener);
	}

	/**
	 * Open the writing stream
	 * Abstract member.
	 */
	btl.gridBase.Renderer.prototype.open = function() {
		// Initialise internal engine variable, without this it has a problem if called from setTimeout
		// XXX: Is this the correct place to do this?
		bb.html.getScrollBarWidth();
	}

	/**
	 * Write to the writing stream
	 * Abstract member.
	 * XXX: sData can also be of type Array; remove this possibility.
	 * @param sData
	 */
	btl.gridBase.Renderer.prototype.write = function(sData) {
		// override
	}

	/**
	 * Closes the writing stream.
	 * Abstract member.
	 */
	btl.gridBase.Renderer.prototype.close = function() {
		// override
		return null;
	}

	/**
	 * Initialises the grid body
	 * @param sData The grid body
	 * @param sCssFile CSS to load (optional)
	 * @return
	 */
	btl.gridBase.Renderer.prototype.init = function(sData, sCssFile) {
		this.open(sCssFile);
		this.write(sData);
		this.close();
		this.ready();
	}

	/**
	 * Called after the stream is closed to initialize the object.
	 * Abstract member.
	 */
	btl.gridBase.Renderer.prototype.ready = function(oElm) {
		var aDivs = btl.gridBase.getChildrenByTagName(oElm, 'div');

		// copy first table into header
		aDivs[0].style.display = 'none';

		//find the number of columns
		var iNCols = 1;
		var eTable = aDivs[0].getElementsByTagName('table')[0];
		if (eTable && eTable.rows.length) //get number of columns from a header table
			iNCols = btl.gridBase.computeCellRanges(eTable).numColumns;
		else {
			eTable = aDivs[1].getElementsByTagName('table')[0];
			if (eTable && eTable.rows.length) //get number of columns from a data table
				iNCols = eTable.rows[0].cells.length;
		}

		this.port.container.header.dataReady(aDivs[0], iNCols);
		this.port.container.body.dataReady(aDivs[1], iNCols);

		//start polling grid changes to keep correct form
		this.grid.startPolling();
	}

	btl.gridBase.Renderer.prototype.getHeadStartTemplate = function() {
		return '<div style="display:none;">';
	}
	btl.gridBase.Renderer.prototype.getHeadEndTemplate = function() {
		return '</div>';
	}
	btl.gridBase.Renderer.prototype.getBodyStartTemplate = function() {
		return '<div style="width:auto;height:auto;">';
	}
	btl.gridBase.Renderer.prototype.getBodyEndTemplate = function() {
		return '</div>';
	}
	btl.gridBase.Renderer.prototype.getTableStartTemplate = function() {
		return '<table class="btl-grid-table" cellpadding="0" cellspacing="0" border="0">';
	}
	btl.gridBase.Renderer.prototype.getTableEndTemplate = function() {
		return '</table>';
	}

	btl.gridBase.Renderer.prototype.getContentHeight = function() {
		return btl.gridBase.getScrollHeight(this.viewNode);
	}



	/**
	 *
	 * @class A viewport to data in a div
	 */
	btl.gridBase.DivRenderer = function(oGrid, oContainer) {
		btl.gridBase.Renderer.call(this, oGrid, oContainer);
		this.writeData = null;
		this.viewNode = oContainer.viewNode;
	}
	btl.gridBase.DivRenderer.prototype = new btl.gridBase.Renderer();

	btl.gridBase.DivRenderer.prototype.open = function() {
		btl.gridBase.Renderer.prototype.open.call(this);
		this.grid.busyIndicator.addBusyState();
		this.writeData = [];
	}

	/**
	 *
	 * XXX: sData can also be of type Array; remove this possibility.
	 * @param sData
	 * @return
	 */
	btl.gridBase.DivRenderer.prototype.write = function(sData) {
		this.writeData.push(sData);
	}

	/**
	 * Closes the writing stream.
	 * oViewNode - optional element to render current data
	 * @return data container element
	 */
	btl.gridBase.DivRenderer.prototype.close = function( oViewNode) {
		(oViewNode ? oViewNode : this.viewNode).innerHTML = this.writeData.join('');
		this.writeData = null;
		return this.viewNode;
	}

	btl.gridBase.DivRenderer.prototype.ready = function() {
		if (this.grid.initialized) {
			// if already initialized, then only the content was re-rendered
			this.port.refreshTables();
			this.grid.doLayout();
			this.grid.fridge.update();
		} else {
			// create internal structures
			btl.gridBase.Renderer.prototype.ready.call(this, this.viewNode);
			this.grid.setInitialized();
		}
		this.grid.busyIndicator.removeBusyState();
	}


	/**
	 *
	 * @class A viewport to data using an IFrame
	 */
	btl.gridBase.IFrameRenderer = function(oGrid, oContainer) {
		btl.gridBase.Renderer.call(this, oGrid, oContainer);
		this.frameDocument = this.viewNode.contentDocument || this.viewNode.contentWindow.document;
		this.port.viewNode.appendChild(this.viewNode); // XXX: here or in BodyPort?
		this.port.addSizeListener(this);
	}
	btl.gridBase.IFrameRenderer.prototype = new btl.gridBase.Renderer();

	btl.gridBase.IFrameRenderer.prototype.getTemplate = function() {
//		oDiv.style.overflow = 'auto';
		var oIFrame = document.createElement('iframe');
		var oThis = this;
		oIFrame.onload = function() {
			if ((this.contentDocument || this.contentWindow.document).body.firstChild)
				oThis.ready();
		}
		oIFrame.className = 'btl-grid-iframe';
		oIFrame.scrolling = 'no';
		oIFrame.frameBorder = 0;
		oIFrame.marginWidth = 0;
		oIFrame.marginHeight = 0;
		oIFrame.style.width = '100%';
		oIFrame.src = 'about:blank';
		if (bb.browser.gecko)	// IFrame style fix
			oIFrame.style.borderBottom = '4px solid #FFF';
		return oIFrame;
	}

	btl.gridBase.IFrameRenderer.prototype.ready = function() {
		// XXX: should this move to the constructor?
		bb.controller.attachDocument(this.grid.controller, this.frameDocument);

		btl.gridBase.Renderer.prototype.ready.call(this, this.frameDocument.body);

		var oThis = this;
		setTimeout(function() {
				// when the browser finished rendering
				oThis.grid.doLayout();
				oThis.grid.setInitialized();
			}, 10);
	}

	btl.gridBase.IFrameRenderer.prototype.open = function(sCssFile) {
		btl.gridBase.Renderer.prototype.open.call(this);

		this.frameDocument.open();
		var gridCssFile = bb.getResource(this.grid.controller, 'gridBase_css');
		if (sCssFile)
			sCssFile = bb.uri.resolveUri(sCssFile, this.grid.controller.getProperty('baseURI'));

		this.frameDocument.write(this.getFrameStartTemplate(gridCssFile, [sCssFile]));
	}

	btl.gridBase.IFrameRenderer.prototype.write = function(sData) {
		this.frameDocument.write(sData);
	}

	btl.gridBase.IFrameRenderer.prototype.close = function() {
		this.frameDocument.write(this.getFrameEndTemplate());
		this.frameDocument.close();
	}

	/**
	 * Width change listener.
	 * Match the size of the container to avoid overflow.
	 * @param sWidth
	 */
	btl.gridBase.IFrameRenderer.prototype.widthChanged = function(sWidth) {
		this.setWidth(sWidth);
	}

	/**
	 * Height change listener.
	 * Match the size of the container to avoid overflow.
	 * @param sHeight
	 */
	btl.gridBase.IFrameRenderer.prototype.heightChanged = function(sHeight) {
		this.setHeight(this.viewNode.offsetHeight + "px");
	}

	btl.gridBase.IFrameRenderer.prototype.getContentHeight = function() {
		var iHeight = btl.gridBase.Renderer.prototype.getContentHeight.call(this);
		return iHeight + (bb.browser.gecko ? 1 : bb.browser.webkit ? 3 : bb.browser.opera ? 4 : 0);
	}

	/**
	 * Returns the start of the IFrame document template
	 * @param {String} sCssStyle CSS to include in a <code>style</code> tag
	 * @param {Array} aCssFiles Array containing CSS file names to link
	 * @return Template for IFrame document start
	 * @type String
	 */
	btl.gridBase.IFrameRenderer.prototype.getFrameStartTemplate = function(sCssStyle, aCssFiles) {
		var aString = [];
		aString.push('<!-- --><html><head>');
		aString.push('<meta http-equiv="content-type" content="text/html; charset=UTF-8" />');

		if (sCssStyle)
			aString.push('<style type="text/css">' + sCssStyle + '</style>');

		if (aCssFiles)
			for(var i = 0; i < aCssFiles.length; i++)
				aString.push('<link rel="stylesheet" type="text/css" href="' + aCssFiles[i] + '" />');

		aString.push('</head><body>');
		return aString.join('');
	}

	/**
	 * Returns the end of the IFrame document template
	 * @return Template for IFrame document end
	 * @type String
	 */
	btl.gridBase.IFrameRenderer.prototype.getFrameEndTemplate = function() {
		return '</body></html>';
	}



	/**
	 *
	 * @class
	 */
	btl.gridBase.Scrollers = function(oGrid) {
		btl.gridBase.Container.call(this);
		this.grid = oGrid;
		this.scrollBar = this.add(this.grid.createVScrollBar());
		oGrid.addPollListener(this.scrollBar); //listen to scroll height
	}
	btl.gridBase.Scrollers.prototype = new btl.gridBase.Container();

	btl.gridBase.Scrollers.prototype.getTemplate = function() {
		var oDiv = document.createElement('div');
		oDiv.className = 'btl-grid-scrollers';
		oDiv.style.display = 'none';
		return oDiv;
	}

	btl.gridBase.Scrollers.prototype.show = function() {
		if (this.isHidden()) {
			this.viewNode.style.display = 'block';
			var sSize = bb.html.getScrollBarWidth();
			this.viewNode.style.width = sSize + 'px';
			if (bb.browser.webkit)
				for (var i = 0; i < this.viewNode.childNodes.length; i++)
					this.viewNode.childNodes[i].style.width = sSize + 1 + 'px';
		}
	}

	btl.gridBase.Scrollers.prototype.hide = function() {
		if (!this.isHidden())
			this.viewNode.style.display = 'none';
	}

	btl.gridBase.Scrollers.prototype.isHidden = function() {
		return this.viewNode.style.display == 'none';
	}



	/**
	 * XXX: Rename to VerticalScrollBar? Make a base class ScrollBar?
	 */
	btl.gridBase.VScrollBar = function() {
		btl.gridBase.Component.call(this);
	}
	btl.gridBase.VScrollBar.prototype = new btl.gridBase.Component();

	btl.gridBase.VScrollBar.prototype.poll = function() {
		//check data area height
		if (this.viewNode.firstChild && this.port.getContentHeight() != btl.gridBase.getScrollHeight(this.viewNode.firstChild)) {
			this.updateScrollHeight();
		}
		//check data area position
		var iCurPos = this.port.viewNode.scrollTop;
		if (this.viewNode.scrollTop != iCurPos) {
			this.setPosition(iCurPos);
		}
	}

	btl.gridBase.VScrollBar.prototype.getTemplate = function() {
		var oDiv = document.createElement('div');
		oDiv.className = 'btl-grid-vscroller';
		var oDiv2 = document.createElement('div');
		oDiv2.style.width = '1px';
		oDiv.appendChild(oDiv2);
		return oDiv;
	}

	/**
	 * Height change listener
	 * @param sHeight
	 */
	btl.gridBase.VScrollBar.prototype.heightChanged = function(sHeight) {
		this.setHeight(sHeight);
	}

	/**
	 * Width change listener
	 * @param sWidth
	 */
	btl.gridBase.VScrollBar.prototype.widthChanged = function(sWidth) {
	}

	btl.gridBase.VScrollBar.prototype.updateScrollHeight = function() {
		this.viewNode.firstChild.style.height = btl.gridBase.getScrollHeight(this.port.viewNode) + 'px';
		this.viewNode.style.top = this.port.viewNode.offsetTop + 'px';
	}

	/**
	 *
	 * @param {VScrollBar} oVScrollBar
	 */
	btl.gridBase.VScrollBar.prototype.setPort = function(oPort) {
		var oThis = this;
		this.port = oPort;
		this.viewNode.onscroll = function() {
			// call scroll listeners
			var iTop = oThis.getPosition();
			for (var i = 0, len = oThis.scrollListeners.length; i < len; i++)
				oThis.scrollListeners[i].positionChanged(iTop);
		}
	}

	btl.gridBase.VScrollBar.prototype.positionChanged = function(iTop) {
		this.setPosition(iTop);
	}


	btl.gridBase.FocusElement = function() {
		btl.gridBase.Component.call(this);
	}

	btl.gridBase.FocusElement.prototype = new btl.gridBase.Component();

	btl.gridBase.FocusElement.prototype.getTemplate = function() {
		var oInput = document.createElement('input');
		oInput.className = 'btl-invisibleFocusInput';
		oInput.style.position = 'absolute';
		oInput.style.top = '-20px';
		oInput.style.left = '-20px';
		oInput.readOnly = true;
		if (bb.browser.ie) {
			oInput.onbeforedeactivate = function() {
				return !this.parentNode.controller.__passingFocus;
			}
		}
		return oInput;
	}

	/**
	 * @class Indicator where column should be inserted.
	 */
	btl.gridBase.ColumnInsertIndicator = function() {
		btl.gridBase.Component.call(this);
	}
	btl.gridBase.ColumnInsertIndicator.prototype = new btl.gridBase.Component();

	btl.gridBase.ColumnInsertIndicator.prototype.getTemplate = function() {
		var oDiv = document.createElement('div');
		oDiv.className = 'btl-grid-column-insert-indicator';
		oDiv.style.display = 'none';
		oDiv.appendChild(document.createTextNode(' '));
		return oDiv;
	}

	/**
	 * Shows the indicator for inserting columns at some location.
	 * @param {Number} x X-location relative to (TODO: what?)
	 * @param {Number} x Y-location relative to (TODO: what?)
	 */
	btl.gridBase.ColumnInsertIndicator.prototype.show = function(x, y) {
		this.viewNode.style.display = 'block';
		bb.html.position(this.viewNode, null, "at-pointer", x, y);
	};

	/**
	 * Hide column insertion indicator
	 */
	btl.gridBase.ColumnInsertIndicator.prototype.hide = function() {
		this.viewNode.style.left = '';
		this.viewNode.style.top = '';
		this.viewNode.style.display = 'none';
	}


	/**
	 * @class The ResizeShadow is the transparent overlay that is shown when resizing or dragging columns.
	 * XXX: As it is also used for the latter, the class name is a little deceptive.
	 */
	btl.gridBase.ResizeShadow = function() {
		btl.gridBase.Component.call(this);
	}
	btl.gridBase.ResizeShadow.prototype = new btl.gridBase.Component();

	btl.gridBase.ResizeShadow.prototype.getTemplate = function() {
		var oDiv = document.createElement('div');
		oDiv.className = 'btl-grid-resize-shadow';
		oDiv.style.display = 'none';
// XXX: this is no longer necessary, I think... (confirm with Edward)
//		if (bb.browser.ie) // For IE, CSS class is not enough
//			oDiv.style.backgroundColor = '#999999';
		bb.html.setStyle(oDiv, 'opacity', '0.5');
		return oDiv;
	}

	btl.gridBase.ResizeShadow.prototype.hide = function() {
		this.viewNode.style.display = 'none';
		this.viewNode.style.height = '0px';
	}

	/**
	 * Shows a shadowy indicator of the column that is being dragged
	 * TODO: Instead of dimensions, accept a column object and retrieve dimensions from there
	 * @param iLeft Left position in pixels
	 * @param iWidth Width in pixels
	 * @param iHeight Height in pixels
	 */
	btl.gridBase.ResizeShadow.prototype.showColumnDrag = function(iLeft, iWidth, iHeight) {
		this.viewNode.style.left = iLeft + 'px';
		this.viewNode.style.height = iHeight + 'px';
		this.viewNode.style.width = iWidth + 'px';
		this.viewNode.style.display = 'block';
	}


	btl.gridBase.ResizeDummy = function() {
		btl.gridBase.Component.call(this);
	}
	btl.gridBase.ResizeDummy.prototype = new btl.gridBase.Component();

	btl.gridBase.ResizeDummy.prototype.getTemplate = function() {
		var oDiv = document.createElement('div');
		oDiv.className = 'btl-grid-resize-dummy';
		oDiv.style.display = 'none';
		return oDiv;
	}


	/**
	 * Constructs a BusyIndicator instance
	 * @param oGrid The Grid object the component is a part of
	 * @class Busy indicator
	 * TODO: instead of showing the indicator, just throw events and let the control deal with it.
	 *       probably move the methods to the Grid object.
	 */
	btl.gridBase.BusyIndicator = function(oGrid) {
		btl.gridBase.Component.call(this);
		this.grid = oGrid;
		this.busyCount = 0;
	}
	btl.gridBase.BusyIndicator.prototype = new btl.gridBase.Component();

	btl.gridBase.BusyIndicator.prototype.getTemplate = function() {
		var oDiv = document.createElement('div');
		oDiv.className = 'btl-grid-busy-indicator';
		return oDiv;
	}

	/**
	 * Makes the state busy, and adds 1 to the counter.
	 */
	btl.gridBase.BusyIndicator.prototype.addBusyState = function() {
		var iOld = this.busyCount;
		this.busyCount++;
		if (!iOld && !bb.command.fireEvent(this.grid.controller, 'busyOn', false, true).defaultPrevented) {//0->1
			this.viewNode.style.display = 'block';
		}
	}

	/**
	 * Removes 1 from the counter, and makes the state not busy if it reaches 0.
	 */
	btl.gridBase.BusyIndicator.prototype.removeBusyState = function() {
		if (this.busyCount > 0) {
			this.busyCount--;
			if (!this.busyCount && !bb.command.fireEvent(this.grid.controller, 'busyOff', false, true).defaultPrevented) {//1->0
				this.viewNode.style.display = 'none';
			}
		}
	}

	/**
	 * Returns whether the state is busy.
	 * @return <code>true</code> if the state is busy.
	 * @type Boolean
	 */
	btl.gridBase.BusyIndicator.prototype.isBusy = function() {
		return this.busyCount > 0;
	}


	btl.gridBase.DragDummy = function() {
		btl.gridBase.Component.call(this);
	}
	btl.gridBase.DragDummy.prototype = new btl.gridBase.Component();

	btl.gridBase.DragDummy.prototype.getTemplate = function() {
		var oDiv = document.createElement('div');
		oDiv.className = 'btl-grid-drag-dummy';
		return oDiv;
	}



	/**
	 *
	 * @class
	 * XXX: To get a column, support grid.columns[iCol] in addition to grid.columns.getColumn(iCol)?
	 */
	btl.gridBase.ColumnList = function(oGrid) {
		this.grid = oGrid;
		this.columns = [];
		this.moveListeners = [];
		this.port = null;
		// The column to accept extra width
		// TODO: update on column hide
		// XXX: simplify determination algorithm? make getLastFreeColumn() getter?
		this.lastFreeColumn = null;
		this.widthsInitialized = false;
	}
	btl.gridBase.ColumnList.prototype = {};

	/**
	 * Add column move listener. Interface: moveColumns(iOld, iNew, iLength).
	 * @return
	 */
	btl.gridBase.ColumnList.prototype.addMoveListener = function(oMoveListener) {
		btl.gridBase.addListener(this.moveListeners, oMoveListener);
	}

	/**
	 * Remove column move listener. Interface: moveColumns(iOld, iNew, iLength).
	 * @return
	 */
	btl.gridBase.ColumnList.prototype.removeMoveListener = function(oMoveListener) {
		btl.gridBase.removeListener(this.moveListeners, oMoveListener);
	}

	/**
	 * Creates column indexes for every table to support row/col spanning
	 * @param {Port} The port containing the header cells
	 */
	btl.gridBase.ColumnList.prototype.initialize = function(oPort) {
		this.port = oPort;

		// create Column objects
		this.columns = [];
		var oRow = oPort.getTableRow(0, 0);	// get sizer row
		for (var i = 0, len = oRow.cells.length; i < len; i++) {
			this.columns.push(this.grid.createColumn(i, oRow.cells[i]));
		}

		oPort.refreshRanges();
		this.refreshColumnIndexes();
	}

	/**
	 * Fills in the tableIndex property of each Column object,
	 * pointing to the bottom-most heading cell for each column.
	 */
	btl.gridBase.ColumnList.prototype.refreshColumnIndexes = function() {
		var oPort = this.port;
		var aCellIndexes = [];
		for (var i = 0; i < oPort.ranges.length; i++) {
			var aColumns = oPort.ranges[i];
			for (var j = 0; j < aColumns.length; j++) {
				var oRange = aColumns[j];
				if (oRange.first == oRange.last)
					aCellIndexes[oRange.first] = new btl.gridBase.TableIndex(oPort, 0, i, j);
			}
		}
		for (var i = 0, len = this.columns.length; i < len; i++)
			this.columns[i].tableIndex = aCellIndexes[i];
	}

	/**
	 * @return Number of columns
	 * @type Integer
	 */
	btl.gridBase.ColumnList.prototype.getLength = function() {
		return this.columns.length;
	}

	/**
	 * Get a column by its index
	 * @param iIndex
	 * @return
	 */
	btl.gridBase.ColumnList.prototype.getColumn = function(iIndex) {
		return this.columns[iIndex];
	}

	/**
	 * Get a column object from a corresponding table cell
	 * @param iIndex
	 * @return
	 * @type Column
	 */
	btl.gridBase.ColumnList.prototype.getColumnFromCell = function(oCell) {
		var oIndexes = this.port.getRange(oCell);
		if (oIndexes)
			return this.getColumn(oIndexes.first);
		return null;
	}

	/**
	 * Get an array with column objects from a corresponding table cell
	 * @param iIndex
	 * @return
	 * @type Array
	 */
	btl.gridBase.ColumnList.prototype.getColumnsFromCell = function(oCell) {
		var oIndexes = this.port.getRange(oCell);
		var aColumns = [];
		if (oIndexes) {
			for (var i = oIndexes.first; i <= oIndexes.last; i++)
				aColumns.push(this.getColumn(i));
		}
		return aColumns;
	}

	/**
	 * Moves a column from one index to another.
	 * TODO: It is currently not possible yet to move columns out of a colspanned header yet.
	 * The parameters need to be chosen carefully to ensure that this doesn't happen.
	 * @param iOld Old index
	 * @param iNew New index
	 * @param iLength Number of columns (optional)
	 */
	btl.gridBase.ColumnList.prototype.move = function(iOld, iNew, iLength) {
		if (iLength == undefined)
			iLength = 1;
		this.grid.busyIndicator.addBusyState();

		var aOld = this.columns.splice(iOld, iLength);
		// A pseudo-code version of the following lines would be:
		// this.columns.splice(iNew, 0, aOld[0], ..., aOld[n])
		aOld.unshift(iNew > iOld ? iNew - iLength : iNew, 0);
		this.columns.splice.apply(this.columns, aOld);
		// update index property
		for (var i = 0, len = this.columns.length; i < len; i++)
			this.columns[i].index = i;

		// call listeners
		for (var i = 0, len = this.moveListeners.length; i < len; i++)
			this.moveListeners[i].columnsMoved(iOld, iNew, iLength);

		this.port.refreshRanges();
		this.refreshColumnIndexes();
		this.grid.busyIndicator.removeBusyState();
	}

	/**
	 * Retrieves the total width of the columns
	 * XXX: maybe rename to just getWidth?
	 * @return Total width in pixels
	 * @type Integer
	 */
	btl.gridBase.ColumnList.prototype.getTotalWidth = function() {
		var iWidth = 0;
		for (var i = 0, len = this.columns.length; i < len; i++)
			iWidth += bb.html.convertToPixels(this.columns[i].getWidth(), this.grid.viewNode);
		return iWidth;
	}

	/**
	 * Calculates and sets all initial sizes of the columns in the grid.
	 * Also corrects the size of one of the columns to fully fill initial
	 * layout. In case if grid width is "auto" the previous action does
	 * not take place, instead grid container is adjusted to the table size.
	 * TODO: change width control to col elements
	 * XXX: shouldn't each Column object's constructor deal with width initialisation?
	 * @return
	 */
	btl.gridBase.ColumnList.prototype.initWidth = function() {
		// XXX: back compat; array of grid column restore widths
		var aRestoreWidths = this.grid.aRestoreWidths;

		// the index of the column that can expand its size
		var iLastFreeColumn = this.columns.length - 1;
		// taking last width
		var sDefaultWidth = this.grid.controller.getProperty('defaultColumnWidth');

		for (var i = 0, len = this.columns.length; i < len; i++) {
			var oColumn = this.columns[i];
			var sWidth = oColumn.getWidth();
			if (!sWidth || sWidth == 'auto') {
				// set default width on columns that have no width
				iLastFreeColumn = i;
				sWidth = sDefaultWidth;
			}
			oColumn.restoreWidth = aRestoreWidths[i] || null;
			// XXX: init the lastFreeColumn width here as well...
			oColumn.setWidth(sWidth);
		}

		// for auto width we need now prepare the  inner container to wrap the inner table with correct size
		if (this.grid.controller.getProperty('width') == 'auto')
			this.grid.dataContainer.body.viewNode.style.width = this.getTotalWidth() + "px";

		// marking the grid that it was initialized with widths
		// XXX: necessary?
		this.widthsInitialized = true;
		this.lastFreeColumn = this.columns[iLastFreeColumn];
	}

	/**
	 * If a grid has fixed width and the data area is smaller than it, adjust one column's width to fit.
	 */
	btl.gridBase.ColumnList.prototype.fillFreeColumn = function() {
		// expanding of the column makes sense only in the case of not auto width of the grid
		if (this.grid.controller.getProperty('width') != 'auto') {
			var iInnerWidth = this.port.container.viewNode.scrollWidth; //count border for IE, Opera
			var iUsedWidth = this.port.getTable(0).scrollWidth;
			if (iUsedWidth < iInnerWidth) {
				var oLastFreeColumn = this.lastFreeColumn;
				var iWidth = bb.html.convertToPixels(oLastFreeColumn.getWidth()) + (iInnerWidth - iUsedWidth);
				oLastFreeColumn.setWidth(iWidth + 'px');
			}
		}
	}



	/**
	 *
	 * @class
	 */
	btl.gridBase.Column = function(oGrid, iIndex, oViewNode) {
		this.grid = oGrid;
		this.index = iIndex;
		this.viewNode = oViewNode;
		this.tableIndex = null;	// set by refreshColumnIndexes in ColumnList
		this.restoreWidth = null;
		this.hidden = false;
	}
	btl.gridBase.Column.prototype = {};

	/**
	 * Returns header sizer cell
	 * @param {Number} column A column index
	 * @return Header sizer cell
	 * @type Element
	 */
	btl.gridBase.Column.prototype.getSizer = function() {
		return (this.isFrozen() ? this.grid.fridge : this.grid.dataContainer).header.getTableCell(0, 0, this.index);
	}

	btl.gridBase.Column.prototype.isFrozen = function() {
		// XXX: remove isFrozen from the Fridge and make frozenColumns property instead
		return this.grid.fridge.isFrozen(this.index);
	}

	/**
	 * Adjusts column width.
	 * @param sWidth New column width in CSS units
	 */
	btl.gridBase.Column.prototype.setWidth = function(sWidth) {
		// XXX: generic 'call on all views' pattern needed
		var aViews = [];
		aViews.push(this.grid.dataContainer.header);
		aViews.push(this.grid.dataContainer.body);
		if (!this.grid.fridge.isEmpty()) {
			aViews.push(this.grid.fridge.header);
			aViews.push(this.grid.fridge.body);
		}

		for (var k = 0; k < aViews.length; k++) {
			aViews[k].setColumnWidth(this.index, sWidth);
		}
	}

	/**
	 * Returns the current column width
	 */
	btl.gridBase.Column.prototype.getWidth = function() {
		return this.viewNode.style.width; // || this.viewNode.offsetWidth + 'px';
	}

	/**
	 * Hides column(s)
	 */
	btl.gridBase.Column.prototype.hide = function() {
		if (!this.isHidden()) {
			this.restoreWidth = this.getWidth();
			this.hidden = true;
			this.setWidth('0px');
		}//if
	}

	/**
	 * Returns true if a column is hidden
	 */
	btl.gridBase.Column.prototype.isHidden = function() {
//		return this.hidden;
		// TODO: probably doing this is better (as server response may vary).
		// Note however that originalWidth is not available if you do this...
		return this.getSizer().offsetWidth == 0;
	}

	/**
	 * Unhides column(s)
	 */
	btl.gridBase.Column.prototype.show = function() {
		if (!this.isHidden())
			return;
		// XXX: if !this.restoreWidth set default width?
		this.setWidth(this.restoreWidth);
		this.hidden = false;
	}


	/**
	 * An object that points to a location in a table
	 * TODO: create factory method (?)
	 * @param {Port} oPort
	 * @param {Number} iTable
	 * @param {Number} iRow
	 * @param {Number} iCell
	 */
	btl.gridBase.TableIndex = function(oPort, iTable, iRow, iCell) {
		this.type = iRow == undefined ? this.TYPE_TABLE : iCell == undefined ? this.TYPE_ROW : this.TYPE_CELL;
		this.port = oPort;
		this.table = iTable;
		this.row = iRow;
		this.cell = iCell;
	}
	btl.gridBase.TableIndex.prototype = {};

	btl.gridBase.TableIndex.prototype.TYPE_TABLE = 1;
	btl.gridBase.TableIndex.prototype.TYPE_ROW = 2;
	btl.gridBase.TableIndex.prototype.TYPE_CELL = 3;

	btl.gridBase.TableIndex.prototype.clone = function() {
		return new btl.gridBase.TableIndex(this.port, this.table, this.row, this.cell);
	}

	/**
	 * Compare the position of this table index with that of another.
	 * @param oTableIndex The TableIndex object to compare to.
	 * @return -1 if this comes before the argument,
	 *         0 if they are at the same position,
	 *         1 if this comes after the argument
	 */
	btl.gridBase.TableIndex.prototype.compareTo = function(oTableIndex) {
		if (this.table < oTableIndex.table)
			return -1
		if (this.table > oTableIndex.table)
			return 1;
		if (this.row < oTableIndex.row)
			return -1
		if (this.row > oTableIndex.row)
			return 1;
		if (this.cell < oTableIndex.cell)
			return -1
		if (this.cell > oTableIndex.cell)
			return 1;
		return 0;
	}

	/**
	 * Increases or decreases the table row with the offset.
	 * @param {Number} iOffset
	 * @return Whether the offset exceeded the table size.
	 */
	btl.gridBase.TableIndex.prototype.offsetRow = function(iOffset) {
		this.row += iOffset;
		var oTable = this.port.tables[this.table];
		while (this.row < 1) {
			if (this.table > 0) {
				this.table--;
				oTable = this.port.tables[this.table];
				this.row += oTable.rows.length - 1; // skip sizer row
			} else {
				this.row = 1; // skip sizer row
				return false;
			}
		}
		while (this.row >= oTable.rows.length) {
			if (this.table < this.port.tables.length - 1) {
				this.row -= oTable.rows.length - 1; // skip sizer row
				this.table++;
				oTable = this.port.tables[this.table];
			} else {
				this.row = oTable.rows.length - 1; // skip sizer row
				return false;
			}
		}
		return true;
	}

	btl.gridBase.TableIndex.prototype.nextRow = function() {
		return this.offsetRow(1);
	}

	btl.gridBase.TableIndex.prototype.previousRow = function() {
		return this.offsetRow(-1);
	}

	btl.gridBase.TableIndex.prototype.getElement = function() {
		return this.getElementFromPort(this.port);
	}

	btl.gridBase.TableIndex.prototype.getElementFromPort = function(oPort) {
		switch (this.type) {
			case this.TYPE_CELL:
				return oPort.getTableCell(this.table, this.row, this.cell);
			case this.TYPE_ROW:
				return oPort.getTableRow(this.table, this.row);
			case this.TYPE_TABLE:
				return oPort.getTable(this.table);
		}
		return null;
	}

	/**
	 *
	 * @class An object that indicates a range of cells. Both first and last are inclusive.
	 * TODO: create factory method (?)
	 */
	btl.gridBase.Range = function(iFirst, iLast) {
		this.first = iFirst;
		this.last = iLast;
		this.length = iLast - iFirst + 1;
	}
	btl.gridBase.Range.prototype = {};
}]]></d:resource>

			<d:method name="createGrid">
				
				<d:argument name="renderMode">
					
				</d:argument>
				<d:body type="text/javascript"><![CDATA[
					return new btl.gridBase.Grid(this, renderMode);
				]]></d:body>
			</d:method>

			<d:template type="text/javascript">
				// XXX: stuff that really belongs in the constructor...
				{
					var sRenderMode = this.getAttribute('renderMode').toUpperCase();

					// instantiate grid object
					var oGrid = this.createGrid(sRenderMode);
					this._._gate = oGrid;

					this._._iChunkSize = 25;		// number of rows per table
				}

				//initially define 2 view ports - one for header and one for data
				//more can be created after (for row/column freezing)
				var oElm = oGrid.viewNode;

				return [oElm, oElm];
			</d:template>

			<d:attribute name="frozenColumns" default="0">
				
				<d:changer type="text/javascript"><![CDATA[
					this._._gate.fridge.freeze(parseInt(value));
				]]></d:changer>
			</d:attribute>

			<d:attribute name="renderMode" default="DIV">
				
			</d:attribute>

			<d:attribute name="defaultColumnWidth">
				
			</d:attribute>

			<d:attribute name="width">
				
				<d:mapper type="text/javascript"/><!-- preventing default mapper -->
				<d:changer type="text/javascript"><![CDATA[
					this.getProperty('gate').doLayout();
				]]></d:changer>
			</d:attribute>

			<d:attribute name="height">
				<d:mapper type="text/javascript"/><!-- preventing default mapper -->
				<d:changer type="text/javascript"><![CDATA[
					this.getProperty('gate').doLayout();
				]]></d:changer>
			</d:attribute>

			<d:property name="focusElement" onget="return this._._gate.focusElement.viewNode"/>

			<d:property name="defaultColumnWidth">
				<d:getter type="text/javascript"><![CDATA[
					var sValue = this.getAttribute('defaultColumnWidth');
					return sValue || '100px';
				]]></d:getter>
				<d:setter type="text/javascript"><![CDATA[
					this.setAttribute('defaultColumnWidth', value);
				]]></d:setter>
			</d:property>

			<d:property name="width">
				<d:getter type="text/javascript"><![CDATA[
					var sValue = this.getAttribute('width');
					return sValue || 'auto';
				]]></d:getter>
				<d:setter type="text/javascript"><![CDATA[
					this.setAttribute('width', value);
				]]></d:setter>
			</d:property>

			<d:property name="height">
				<d:getter type="text/javascript"><![CDATA[
					var sValue = this.getAttribute('height');
					return sValue || 'auto';
				]]></d:getter>
				<d:setter type="text/javascript"><![CDATA[
					this.setAttribute('height', value);
				]]></d:setter>
			</d:property>

			<d:property name="gate">
				<!-- not public API -->
				<d:setter type="text/javascript"><![CDATA[
					bb.command.trace(this, "'gate' property is read only", 3)
				]]></d:setter>
			</d:property>

			<d:property name="busy">
				<!-- not public API -->
				<d:setter type="text/javascript"><![CDATA[
					if (value)
						this._._gate.busyIndicator.addBusyState();
					else
						this._._gate.busyIndicator.removeBusyState();
				]]></d:setter>
				<d:getter type="text/javascript"><![CDATA[
					return this._._gate.busyIndicator.isBusy();
				]]></d:getter>
			</d:property>

			<d:method name="freeze">
				
				<d:argument name="count"/>
				<d:body type="text/javascript"><![CDATA[
					this.setAttribute('frozenColumns', count);
				]]></d:body>
			</d:method>

			<d:constructor type="text/javascript"><![CDATA[
				this.setProperty('busy', true);
				// initializing internal properties
				bb.ui.reflow.add(this);
				this.setProperty('busy', false);
				//add behavior
//				if ( !bb.instanceOf(this, btl.namespaceURI, 'gridColumnDnD'))
//					bb.addBehavior(this, btl.namespaceURI, 'gridColumnDnD');

				//focusing in IE fix
				if(bb.browser.ie){
					var oController = this;
					var func = function(){oController.__passingFocus = false;}
					this.addEventListener('mouseup', func, false);
					this.addEventListener('mouseleave', func, false);
					this.addEventListener('destruct', function(){
						oController.removeEventListener('mouseup', func, false);
						oController.removeEventListener('mouseleave', func, false);
					}, false);
				}
			]]></d:constructor>

			<d:handler event="reflow" type="application/javascript"><![CDATA[
				var oGate = this.getProperty('gate');
				if (oGate && oGate.initialized) // XXX: should this check be here or in doLayout?
					oGate.doLayout();
			]]></d:handler>

<!-- COLUMN RESIZE -->
			<d:handler event="mousemove" type="application/javascript"><![CDATA[
			if (!btl.resize.isResizing && !this.getProperty('busy')) {
				var oGrid = this;
				var oGate = oGrid.getProperty('gate');

				var oTarget = bb.html.hasClass(event.viewTarget, oGate.hClassNames.header) ? event.viewTarget : bb.selector.queryAncestor( event.viewTarget, oGate.hSelectors.header);
				if (!oTarget) return;

				if ( bb.html.hasClass( oTarget, oGate.hClassNames.no_resize) ||
						bb.html.hasClass( oGate.dataContainer.header.viewNode, oGate.hClassNames.no_resize))
					return;

				var iClientX = event.pageX;
				var iClientY = event.pageY;

				var oResize = oGate.resize;

				//no check the first cell left edge
				var iTableEdge = btl.resize.detectResizeEdge(oGate.dataContainer.header.viewNode, iClientX, iClientY, 'left', 8);
				if (iTableEdge) { //if table edge - then no resize but could be columns freezing/splitting
					if (oResize.eTarget)
						bb.html.removeClass(oResize.eTarget, ['btl-grid-eResizeCursor', 'btl-grid-sResizeCursor']);
					oResize.eTarget = null;
					oResize.iEdge = 0;
				} else {
					var iCol = null, 	 // column to resize
						iResizeEdge = btl.resize.detectResizeEdge(oTarget, iClientX, iClientY, 'left right', 8); // detected edge

					if (iResizeEdge) { //check the column
						var oIndex = oGate.dataContainer.header.getRange(oTarget);
						if (oIndex) {
							if ( iResizeEdge & btl.resize.LEFT) {
								iCol = oIndex.first - 1;
							} else if (iResizeEdge & btl.resize.RIGHT) {
								iCol = oIndex.last;
							}
						} else
							iResizeEdge = 0;

					} else if ( oGate.fridge.count) { //there are frozen columns - check for the fridge border
						iResizeEdge = btl.resize.detectResizeEdge(oGate.fridge.viewNode, iClientX, iClientY, 'right', 8);
						iCol = oGate.fridge.count - 1;
					}

					if (iResizeEdge) { //check the column
						if ( iCol < 0 || bb.html.hasClass( oGate.columns.getColumn(iCol).getSizer(), oGate.hClassNames.no_resize)) {//turn off resizing
							oTarget = null;
							iResizeEdge = 0;
						} else
							oResize.iColumn = iCol;
					}
					if ( oResize.eTarget != oTarget) {
						if (oResize.eTarget)
							bb.html.removeClass(oResize.eTarget, ['btl-grid-eResizeCursor', 'btl-grid-sResizeCursor']);

						if (iResizeEdge)
							bb.html.addClass(oTarget, 'btl-grid-eResizeCursor');
						oResize.eTarget = oTarget;
					} else {//the same element
						if ( iResizeEdge != oResize.iEdge) {
							if ( iResizeEdge)
								bb.html.addClass(oTarget, 'btl-grid-eResizeCursor');
							else if (oTarget)
								bb.html.removeClass(oTarget, ['btl-grid-eResizeCursor', 'btl-grid-sResizeCursor']);
						}
					}
					oResize.iEdge = iResizeEdge;
				}//tableEdge
			}
			]]></d:handler>

			<d:handler event="mousedown" type="application/javascript"><![CDATA[
				var oGrid = this;
				var oGate = oGrid.getProperty('gate');
				var oResize = oGate.resize;

				// handling column resize
				if (!event.shiftKey && !event.metaKey && !event.ctrlKey && oResize && oResize.iEdge && !event.button) {
					var oTarget = bb.html.hasClass(event.viewTarget, oGate.hClassNames.header) ? event.viewTarget : bb.selector.queryAncestor( event.viewTarget, oGate.hSelectors.header);
					if (!oTarget) return;

					var oShadow = this._._gate.resizeShadow.viewNode;
					var oDummy = this._._gate.resizeDummy.viewNode;
					var eBox = oGate.dataContainer.viewNode;

					//these calculation need to show grid overflow while column resizing
					//is a column in the fridge
					oResize.iLeftLimit = oGate.getLeftLimit(oResize.iColumn);
					oShadow.style.height = eBox.clientHeight + 'px';
					oShadow.style.display = 'block';

					//position and size element to be actually resized
					var oSizer = oGate.columns.getColumn(oResize.iColumn).getSizer();
					var oBox = bb.html.getBoxObject(oSizer);

					//TO-DO: remove FF2 fix
					oBox.width = oSizer.offsetWidth;

					oDummy.style.display = 'block';
					oDummy.style.width = oBox.width + 'px';
					bb.html.position( oDummy, oSizer);

					var iDelta = 0;
					if( (oBox.left + oBox.width) > oResize.iLeftLimit && oBox.left < oResize.iLeftLimit) {//not in fridge and need adjusted
						iDelta = oResize.iLeftLimit - oBox.left;
						oDummy.style.width = (oDummy.offsetWidth - iDelta) + 'px';
						oDummy.style.left = oDummy.offsetLeft + iDelta + 'px';
					}
					oResize.iDelta = iDelta; //resize element fix

					this.setProperty( 'busy', true);
					btl.resize.startResize(
							this, oDummy,
							btl.resize.RIGHT,
							event.pageX,
							event.pageY,
							oShadow, iDelta > 20 ? 0 : (20 - iDelta), 20
						);
					oShadow.style.top =  '0px';
					oShadow.style.height = eBox.clientHeight + 'px';
				}
			]]></d:handler>

			<d:handler event="resize" type="text/javascript"><![CDATA[
				if (event.defaultPrevented) return;
				var oResize = this.getProperty('gate').resize;
				if (oResize.iEdge) {
					if ( oResize.iLeftLimit > event.pageX) {//
						btl.resize.positionCursor(this,  event.pageX, event.pageY, "not-allowed");
						event.preventDefault();
					} else {//Place the cursor
						btl.resize.positionCursor(this,  event.pageX, event.pageY, "e-resize");
					}
				}
			]]></d:handler>

			<d:handler event="resizeEnd" type="application/javascript"><![CDATA[
				var oGrid = this;
				var oGate = oGrid.getProperty('gate');
				var oResize = oGate.resize;

				if (oResize.iEdge) {
					this._._gate.resizeDummy.viewNode.style.display = 'none';
					this._._gate.resizeShadow.hide();
					if (event.originalWidth != event.newWidth) {
						if (oResize.iColumn !== null) {
							oGate.columns.getColumn(oResize.iColumn).setWidth(event.newWidth + oResize.iDelta + 'px');
						}
					}
					if (oResize.eTarget)
						bb.html.removeClass(oResize.eTarget, ['btl-grid-eResizeCursor', 'btl-grid-sResizeCursor']);
					oResize.eTarget = null;
					oResize.iEdge = 0;
					oGate.doLayout();
					btl.resize.positionCursor(null, -100, -100);
					this.setProperty( 'busy', false);
				}
			]]></d:handler>

			<!-- body area scrolling -->
			<d:handler event="mousewheel" type="application/javascript"><![CDATA[
				 if (event.target == this)
					if (!event.defaultPrevented) {
						var oGate = this.getProperty('gate');
						var oTarget = bb.html.hasClass(event.viewTarget, oGate.hClassNames.view) ? event.viewTarget : bb.selector.queryAncestor( event.viewTarget, oGate.hSelectors.view);
						if (!oTarget)
							return;

						oGate.scrollers.scrollBar.setPosition(oGate.scrollers.scrollBar.getPosition() + event.wheelDelta * bb.html.getScrollBarWidth());
						event.preventDefault();
					}
			]]></d:handler>
		</d:element>

		<!-- grid columns drag and drop -->
		<d:behavior name="gridColumnDnD" extends="b:dragBase">
			

			

			<d:handler event="dragStart" type="application/javascript"><![CDATA[
				var oGate = this.getProperty('gate');

				var eColumn = bb.html.hasClass(event.viewTarget, oGate.hClassNames.header) ? event.viewTarget : bb.selector.queryAncestor( event.viewTarget, oGate.hSelectors.header);
				if (!eColumn) return;

				var oColumnIndex = oGate.dataContainer.header.getRange(eColumn);
				if (oGate.fridge.isFrozen(oColumnIndex.first)) {
					event.preventDefault();
				} else if (!event.defaultPrevented) {
					this.setProperty( 'busy', true);

					if ( this._._eDragColumn != eColumn) {
						this._._aHCells = oGate.dataContainer.header.getDropTargets(eColumn);
					}
					var iLeft = eColumn.offsetLeft + eColumn.parentNode.parentNode.offsetLeft;
					if ( !oGate.fridge.isFrozen(oColumnIndex.first))
					 	iLeft -= oGate.dataContainer.viewNode.scrollLeft;
					var iWidth = eColumn.offsetWidth + 1;
					var iLeftLimit = oGate.getLeftLimit( oColumnIndex.first, true);
					if ( iLeftLimit > iLeft){
						iWidth -= iLeftLimit - iLeft;
						iLeft = iLeftLimit;
					}

					this._._gate.resizeShadow.showColumnDrag(iLeft - 1, iWidth,
								oGate.dataContainer.viewNode.clientHeight);

					this._._eDragColumn = eColumn; //what column is dragging
					this._._gate.dragDummy.viewNode.innerHTML = eColumn.innerHTML;
					//avoid wide DIV
					this._._gate.dragDummy.viewNode.style.width = eColumn.offsetWidth + 'px';
					btl.drag.dragManager.doDrag(
						this, eColumn, event.startX, event.startY,
						this._._gate.dragDummy.viewNode, 10, 10, 1, false, null
					);
				}
			]]></d:handler>

			<d:handler event="drag" type="application/javascript"><![CDATA[
				if ( this._._aHCells && this._._aHCells.length) {
					var oGate = this.getProperty('gate');
					var oTarget = bb.html.hasClass(event.viewTarget, oGate.hClassNames.header) ? event.viewTarget : bb.selector.queryAncestor( event.viewTarget, oGate.hSelectors.header);
					if (!oTarget) return;

					for (var i = 0; this._._aHCells.length > i; i++)
						if (this._._aHCells[i] == oTarget) { //found acceptable target
							this._._eCurrentTarget = oTarget;

							// handling insert indicator
							var oBox = bb.html.getBoxObject( oTarget);
							var bBefore = event.pageX - oBox.width / 2 < oBox.left;
							this._._bInsertAfter = !bBefore;

							var x = bBefore ? oBox.left : (oBox.left + oBox.width);
							var y = bb.html.getBoxObject( oTarget.parentNode).top;//fix for Safari
							this._._gate.columnInsertIndicator.show(x, y);
							// accepting drop
							btl.drag.dragManager.acceptDragDrop( this);
							bb.html.setStyle(this._._gate.dragDummy.viewNode, 'opacity', 1);
							this._._gate.dragDummy.viewNode.style.backgroundColor = '';
							return;
						}
				}
				// declining drop
				bb.html.setStyle(this._._gate.dragDummy.viewNode, 'opacity', 0.3);
				this._._eCurrentTarget = null;
				this._._gate.columnInsertIndicator.hide();
				btl.drag.dragManager.acceptDragDrop( null);
			]]></d:handler>

			<d:handler event="dragDrop" type="application/javascript"><![CDATA[
				if (this._._eCurrentTarget) {
					var oGate = this.getProperty('gate');
					var oSourceIndex = oGate.dataContainer.header.getRange(this._._eDragColumn);
					var oIndex = oGate.dataContainer.header.getRange(this._._eCurrentTarget);
					var iTargetIndex = this._._bInsertAfter ? oIndex.last + 1 : oIndex.first;
					if ((oSourceIndex.last + 1) != iTargetIndex && oSourceIndex.first != iTargetIndex) { //need move
						oGate.columns.move(oSourceIndex.first, iTargetIndex, oSourceIndex.length);
						bb.command.fireEvent(this, 'columnsReordered', false, false);
					}
				} else {
					event.preventDefault();
				}
			]]></d:handler>

			<d:handler event="dragEnd" type="application/javascript"><![CDATA[
				this._._eDragColumn = null;
				this._._eCurrentTarget = null;
				this._._aHCells = null;

				var eDummy = this._._gate.dragDummy.viewNode;
				eDummy.style.left = "";
				eDummy.style.top = "";

				// hiding insert indicator
				this._._gate.columnInsertIndicator.hide();
				this._._gate.resizeShadow.hide();
				this.setProperty('busy', false);
			]]></d:handler>
		</d:behavior>
	</d:namespace>
</d:tdl>